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
## Breaking Changes
* 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
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 '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/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
@ -15,6 +14,8 @@ class NeedUpdate {
final String groupID;
final UpdateType updateType;
final String extraData;
int? groupInfoSubType;
String? ownerID;
NeedUpdate(this.groupID, this.updateType, this.extraData);
}
@ -44,10 +45,11 @@ class TUIGroupListenerModel extends ChangeNotifier {
onMemberKicked: (groupID, opUser, memberList) async {
if (_isLoginUserKickedFromGroup(groupID, memberList)) {
_deleteGroupConversation(groupID);
}
final groupName = await _getGroupName(groupID);
_needUpdate = NeedUpdate(groupID, UpdateType.kickedFromGroup, groupName);
notifyListeners();
}
},
onMemberEnter: (String groupID, List<V2TimGroupMemberInfo> memberList) {
_needUpdate = NeedUpdate(groupID, UpdateType.memberList, "");
@ -59,6 +61,12 @@ class TUIGroupListenerModel extends ChangeNotifier {
},
onGroupInfoChanged: (groupID, changeInfos) {
_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();
},
onReceiveJoinApplication:

View File

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

View File

@ -25,7 +25,7 @@ class TUIGroupProfileModel extends ChangeNotifier {
List<V2TimGroupMemberFullInfo?>? _groupMemberList;
String _groupMemberListSeq = "0";
V2TimGroupInfo? _groupInfo;
Function(String userID, TapDownDetails? tapDetails)? onClickUser;
Function(V2TimGroupMemberFullInfo groupMemberFullInfo, TapDownDetails? tapDetails)? onClickUser;
GroupProfileLifeCycle? get lifeCycle => _lifeCycle;
@ -233,6 +233,33 @@ class TUIGroupProfileModel extends ChangeNotifier {
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() {
final groupType = _groupInfo?.groupType;
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 &&
await _lifeCycle!.shouldDeleteFriend(userID) == false) {
return null;
@ -152,9 +152,13 @@ class TUIProfileViewModel extends ChangeNotifier {
userIDList: [userID],
deleteType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH);
if (res != null) {
_conversationService.deleteConversation(conversationID: "c2c_$userID");
if (needUpdateData) {
loadData(userID: userID);
}
return res.first;
}
return null;
}
@ -171,49 +175,6 @@ class TUIProfileViewModel extends ChangeNotifier {
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 {
if (_lifeCycle?.shouldAddFriend != null &&
await _lifeCycle!.shouldAddFriend(userID) == false) {
@ -225,6 +186,7 @@ class TUIProfileViewModel extends ChangeNotifier {
loadData(userID: userID);
return res.data;
}
return null;
}
@ -291,13 +253,14 @@ class TUIProfileViewModel extends ChangeNotifier {
newSelfInfo,
),
);
if (res.code == 0) {
newSelfInfo.forEach((key, value) {
updateUserInfo(key, value);
});
notifyListeners();
}
return res;
}
}

View File

@ -50,10 +50,12 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
String localMsgIDListKey = "TUIKit_conversation_list";
late V2TimAdvancedMsgListener advancedMsgListener;
int _unreadCountForConversation = 0;
int _unreadCountForTongue = 0;
// use for generate a new sliver list to show received message list
int _receivedNewMessageCount = 0;
final List<V2TimMessage> _receivedUnreadMessageList = [];
TIMUIKitChatConfig chatConfig = const TIMUIKitChatConfig();
List<V2TimGroupApplication>? _groupApplicationList;
String Function(V2TimMessage message)? _abstractMessageBuilder;
@ -170,15 +172,23 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
return _totalUnreadCount;
}
int get receivedMessageListCount {
return _receivedNewMessageCount;
set totalUnReadCount(int newValue) {
_totalUnreadCount = newValue;
notifyListeners();
}
int get receivedNewMessageCount => _receivedNewMessageCount;
set receivedNewMessageCount(int value) {
_receivedNewMessageCount = value;
}
int get unreadCountForConversation => _unreadCountForConversation;
int get unreadCountForTongue => _unreadCountForTongue;
set unreadCountForTongue(int value) {
_unreadCountForTongue = value;
notifyListeners();
}
List<V2TimGroupApplication> get groupApplicationList => _groupApplicationList ?? [];
@ -201,9 +211,15 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (_currentConversationList.isNotEmpty) {
_currentConversationList.removeLast();
}
_receivedUnreadMessageList.clear();
// notifyListeners();
}
void removeMessageList(String conversationID) {
_messageListMap.remove(conversationID);
}
V2TimMessageReceipt? getMessageReadReceipt(String msgID) {
return messageReadReceiptMap[msgID];
}
@ -250,16 +266,6 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
_groupApplicationList = value;
}
set unreadCountForConversation(int value) {
_unreadCountForConversation = value;
notifyListeners();
}
set totalUnReadCount(int newValue) {
_totalUnreadCount = newValue;
notifyListeners();
}
setChatConfig(TIMUIKitChatConfig config) {
chatConfig = config;
}
@ -324,7 +330,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
notifyListeners();
}
clearRecivedNewMessageCount() {
clearReceivedNewMessageCount() {
_receivedNewMessageCount = 0;
}
@ -523,12 +529,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
_checkFromUserisActive(msgComing);
final convType = TencentUtils.checkString(newMsg.groupID) != null ? ConvType.group : ConvType.c2c;
if (convID != null && convID == currentSelectedConv) {
final position = getMessageListPosition(convID);
if (position == HistoryMessagePosition.notShowLatest) {
return;
}
if (position == HistoryMessagePosition.bottom && unreadCountForConversation == 0) {
_unreadCountForConversation = 0;
// when receive new message in the current chat page, we need to mark the message as read.
if (chatConfig.isAutoReportRead) {
Future.delayed(const Duration(seconds: 1), () {
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;
final tempCurrentMsgList = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...tempCurrentMsgList];
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 {
if (convID == currentSelectedConv) {
unreadCountForConversation++;
unreadCountForTongue++;
_receivedNewMessageCount++;
_receivedUnreadMessageList.add(newMsg);
final currentMsg = _messageListMap[convID] ?? [];
_messageListMap[convID] = [newMsg, ...currentMsg];
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]) {
final activeMessageList = _messageListMap[convID ?? currentSelectedConv];
if (activeMessageList != null) {
@ -581,8 +577,23 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
targetItem.status = MessageStatus.V2TIM_MSG_STATUS_LOCAL_REVOKED;
targetItem.id = DateTime.now().millisecondsSinceEpoch.toString();
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;
notifyListeners();
}
@ -884,15 +895,10 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
}) async {
String receiver = convType == ConvType.c2c ? convID : '';
String groupID = convType == ConvType.group ? convID : '';
final oldGroupType = groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
final sendMsgRes = await _messageService.sendMessage(
id: id,
receiver: receiver,
needReadReceipt: needReadReceipt ??
chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
((chatConfig.groupReadReceiptPermissionList != null && chatConfig.groupReadReceiptPermissionList!.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null && chatConfig.groupReadReceiptPermisionList!.contains(oldGroupType))),
needReadReceipt: needReadReceipt ?? chatConfig.isShowReadingStatus,
groupID: groupID,
priority: priority,
localCustomData: localCustomData,
@ -916,21 +922,25 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
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;
if (needResetNewMessageCount) {
_receivedNewMessageCount = 0;
}
if (isDeleteMsg) {
HistoryMessagePosition position = getMessageListPosition(conversationID);
if (position == HistoryMessagePosition.awayTwoScreen) {
_historyMessagePositionMap[conversationID] = HistoryMessagePosition.notShowLatest;
}
}
notifyListeners();
}
updateMessage(V2TimValueCallback<V2TimMessage> sendMsgRes, String convID, String id, ConvType convType, GroupReceiptAllowType? groupType, ValueChanged<String>? setInputField) {
List<V2TimMessage> currentHistoryMsgList = _messageListMap[convID] ?? [];
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 targetIndex = findIdIndex == -1 ? currentHistoryMsgList.indexWhere((element) => element.msgID == sendMsgResData.msgID) : findIdIndex;
if (targetIndex != -1) {
@ -941,12 +951,7 @@ class TUIChatGlobalModel extends ChangeNotifier implements TIMUIKitClass {
if (loadingMessage[convID] != null && loadingMessage[convID]!.isNotEmpty) {
loadingMessage[convID]!.removeWhere((element) => element.id == id);
}
final oldGroupType = groupType != null ? GroupReceptAllowType.values[groupType.index] : null;
if (chatConfig.isShowGroupReadingStatus &&
convType == ConvType.group &&
sendMsgRes.data?.msgID != null &&
((chatConfig.groupReadReceiptPermissionList != null && chatConfig.groupReadReceiptPermissionList!.contains(groupType)) ||
(chatConfig.groupReadReceiptPermisionList != null && chatConfig.groupReadReceiptPermisionList!.contains(oldGroupType)))) {
if (chatConfig.isShowReadingStatus && sendMsgRes.data?.msgID != null) {
_messageReadReceiptMap[sendMsgRes.data!.msgID!] = V2TimMessageReceipt(timestamp: 0, userID: "", readCount: 0);
}
_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 {
static const String conversationC2CPrefix = "c2c_";
static const String conversationGroupPrefix = "group_";
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
final ConversationService _conversationService = serviceLocator<ConversationService>();
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
@ -65,6 +68,10 @@ class TUIConversationViewModel extends ChangeNotifier {
return _conversationList;
}
V2TimConversation? getConversation(String conversationID) {
return _conversationList.firstWhere((element) => element?.conversationID == conversationID);
}
String? get scrollToConversation => _scrollToConversation;
set scrollToConversation(String? value) {
@ -124,6 +131,18 @@ class TUIConversationViewModel extends ChangeNotifier {
}
}, onConversationDeleted:(List<String> 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;
}
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 {
final blockListRes = await _friendshipServices.getBlackList();
_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/friendShip/friendship_services.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';
class FriendshipServicesImpl implements FriendshipServices {
@ -77,17 +78,34 @@ class FriendshipServicesImpl implements FriendshipServices {
friendGroup: friendGroup,
addSource: addSource,
);
if (result.code != 0 ||
(result.code == 0 &&
result.data?.resultCode != 0 &&
result.data?.resultCode != 30539 &&
result.data?.resultCode != 30515)) {
if (result.code != 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: result.desc,
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(
type: TIMCallbackType.API_ERROR,
errorMsg: result.code == 0 ? result.data?.resultInfo : result.desc,
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;
}
@ -118,12 +136,18 @@ class FriendshipServicesImpl implements FriendshipServices {
.getFriendshipManager()
.deleteFromFriendList(userIDList: userIDList, deleteType: deleteType);
if (res.code == 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,
errorCode: res.code,
infoRecommendText: TIM_t("好友删除成功")));
return res.data;
} else {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,
errorCode: res.code));
errorCode: res.code,
infoRecommendText: TIM_t("好友删除失败")));
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/group/group_services.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_im_base/tencent_im_base.dart';
@ -249,10 +250,12 @@ class GroupServicesImpl extends GroupServices {
final result = await TencentImSDKPlugin.v2TIMManager
.joinGroup(groupID: groupID, message: message);
if (result.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(result.code);
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: result.desc,
errorCode: result.code));
errorCode: result.code,
infoRecommendText: recommendText));
}
return result;
}

View File

@ -3,9 +3,11 @@
import 'dart:math';
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/message/message_services.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_im_base/tencent_im_base.dart';
@ -271,10 +273,12 @@ class MessageServiceImpl extends MessageService {
.getMessageManager()
.reSendMessage(msgID: msgID, onlineUserOnly: onlineUserOnly ?? false);
if (res.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(res.code);
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: res.desc,
errorCode: res.code));
errorCode: res.code,
infoRecommendText: recommendText));
}
return res;
}
@ -359,10 +363,12 @@ class MessageServiceImpl extends MessageService {
cloudCustomData: cloudCustomData,
);
if (result.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(result.code);
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: result.desc,
errorCode: result.code));
errorCode: result.code,
infoRecommendText: recommendText));
}
return result;
}
@ -471,7 +477,7 @@ class MessageServiceImpl extends MessageService {
return TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.cleanConversationUnreadMessageCount(
conversationID: "c2c_$userID",
conversationID: "${TUIConversationViewModel.conversationC2CPrefix}$userID",
cleanTimestamp: 0,
cleanSequence: 0,
);
@ -486,7 +492,7 @@ class MessageServiceImpl extends MessageService {
return TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.cleanConversationUnreadMessageCount(
conversationID: "group_$groupID",
conversationID: "${TUIConversationViewModel.conversationGroupPrefix}$groupID",
cleanTimestamp: 0,
cleanSequence: 0,
);

View File

@ -81,14 +81,28 @@ class TIMUIKitProfileController {
}
Future<V2TimCallback> updateSelfSignature(String selfSignature) {
return model.updateSelfSignature(selfSignature);
Map<String, dynamic> infoMap = {"selfSignature": selfSignature};
return model.updateSelfInfo(infoMap);
}
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) {
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 'package:audioplayers/audioplayers.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/index.dart';

View File

@ -27,6 +27,16 @@ class TimeAgo {
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) {
final DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000);
final DateTime epochLimit = DateTime.utc(1971);

View File

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

View File

@ -203,29 +203,12 @@ class _SendApplicationState extends TIMUIKitState<SendApplication> {
return;
}
final res = await _friendshipServices.addFriend(
_friendshipServices.addFriend(
userID: userID,
addType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH,
remark: remark,
addWording: addWording,
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("发送"))),
)

View File

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

View File

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

View File

@ -75,7 +75,7 @@ class TIMUIKitHistoryMessageList extends StatefulWidget {
final V2TimMessage? initFindingMsg;
/// 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
final TIMUIKitHistoryMessageListConfig? mainHistoryListConfig;
@ -111,6 +111,7 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
late TIMUIKitHistoryMessageListController _controller;
late AutoScrollController _autoScrollController;
LoadingPlace loadingPlace = LoadingPlace.none;
bool maybeHaveMoreMessageForFind = true;
@override
void initState() {
@ -165,7 +166,7 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
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
loadingPlace = LoadingPlace.top;
const int singleLoadAmount = kIsWeb ? 15 : 40;
@ -204,21 +205,21 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
showCantFindMsg();
}
} else {
if (widget.model.haveMoreData) {
if (maybeHaveMoreMessageForFind) {
// if the target message not in current message list, load more
findingMsg = targetMsg;
final lastMsgId = _getMessageId(widget.messageList.length - 1);
widget.onLoadMore(lastMsgId, LoadDirection.previous, singleLoadAmount);
maybeHaveMoreMessageForFind = await widget.onLoadMore(lastMsgId, LoadDirection.previous, singleLoadAmount);
} else {
showCantFindMsg();
}
}
}
_onScrollToIndexBySeq(String targetSeq) {
_onScrollToIndexBySeq(String targetSeq) async {
// This method called by tongue request jumping to target @ message
loadingPlace = LoadingPlace.top;
const int singleLoadAmount = 40;
// const int singleLoadAmount = 40;
final msgList = widget.messageList;
String lastSeq = "";
for (int i = msgList.length - 1; i >= 0; i--) {
@ -258,9 +259,10 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
showCantFindMsg();
}
} else {
if (widget.model.haveMoreData) {
if (maybeHaveMoreMessageForFind) {
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 {
showCantFindMsg();
}
@ -313,9 +315,9 @@ class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMess
final messageList = widget.messageList;
final globalModel = context.read<TUIChatGlobalModel>();
final receivedNewMessageList = globalModel.receivedMessageListCount;
final shouldShowUnreadMessage = receivedNewMessageList > 0;
final unreadMessageList = _getReceivedMessageList(receivedNewMessageList);
final receivedNewMessageCount = globalModel.receivedNewMessageCount;
final shouldShowUnreadMessage = receivedNewMessageCount > 0;
final unreadMessageList = _getReceivedMessageList(receivedNewMessageCount);
final readMessageList = messageList.sublist(unreadMessageList.length, messageList.length).toList();
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);
}
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/wide_popup.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';
@ -143,25 +144,25 @@ class MessageToolTipItem {
class ToolTipsConfig {
/// Whether to show the reply to a message option.
final bool showReplyMessage;
bool showReplyMessage;
/// Whether to show the multiple-choice option for messages.
final bool showMultipleChoiceMessage;
bool showMultipleChoiceMessage;
/// Whether to show the option to delete a message.
final bool showDeleteMessage;
bool showDeleteMessage;
/// Whether to show the option to recall a message.
final bool showRecallMessage;
bool showRecallMessage;
/// Whether to show the option to copy a message.
final bool showCopyMessage;
bool showCopyMessage;
/// 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.
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.
final Widget? Function(V2TimMessage message, Function() closeTooltip, [Key? key, BuildContext? context])? additionalItemBuilder;
@ -208,10 +209,7 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
final bool showAvatar;
/// message is read status
final bool showMessageReadRecipt;
/// message read status in group
final bool showGroupMessageReadRecipt;
final bool showMessageReadReceipt;
/// allow message can long press
final bool allowLongPress;
@ -280,11 +278,10 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
this.messageItemBuilder,
this.onLongPressForOthersHeadPortrait,
this.showAvatar = true,
this.showMessageReadRecipt = true,
this.showMessageReadReceipt = true,
this.allowLongPress = true,
this.toolTipsConfig,
this.onLongPress,
this.showGroupMessageReadRecipt = false,
this.allowAtUserWhenReply = true,
this.allowAvatarTap = true,
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(
popupDirection: popupDirection,
minimumOutSidePadding: 0,
@ -887,27 +896,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
}
}
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;
}
List<MessageHoverControlItem> getMessageHoverControlBar(TUIChatSeparateViewModel model, TUITheme theme) {
List<MessageHoverControlItem> getWideMessageHoverControlBar(TUIChatSeparateViewModel model, TUITheme theme) {
return [
if (widget.isUseMessageReaction ?? false)
MessageHoverControlItem(
@ -939,7 +928,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
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(
name: TIM_t("转发"),
icon: Icon(
@ -948,7 +937,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
color: hexToColor("8f959e"),
),
onClick: (_) {
model.addToMultiSelectedMessageList(widget.message);
model.setMessageItemChecked(widget.message, true);
TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.forward,
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) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final customHoverBar = widget.customMessageHoverBarOnDesktop != null ? widget.customMessageHoverBarOnDesktop!(message) : null;
final wideHoverTipList = (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null) ? getMessageHoverControlBar(model, theme) : [];
final wideHoverTipList = (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null) ? getWideMessageHoverControlBar(model, theme) : [];
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(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -1041,7 +1036,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
if (isDesktopScreen && isShowWideToolTip && customHoverBar != null) customHoverBar,
if (!isDesktopScreen || (model.chatConfig.isUseMessageHoverBarOnDesktop && customHoverBar == null && !isShowWideToolTip))
const SizedBox(
height: 24,
height: 20,
),
if (isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL)
Container(
@ -1056,18 +1051,17 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
},
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(
future: Future.delayed(const Duration(seconds: 1)),
future: _conditionalDelay(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done && model.hasSendingMessageID(message.id ?? message.msgID!)) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
padding: const EdgeInsets.only(bottom: 3),
margin: const EdgeInsets.only(right: 6),
child: RotationTransition(
turns: Tween(begin: 0.0, end: 1.0).animate(_animationController),
child: Icon(Icons.rotate_right, color: theme.cautionColor, size: 18),
),
width: 12.0,
height: 15.0,
child: CircularProgressIndicator(strokeWidth: 1.0),
);
} else {
return Container();
@ -1075,21 +1069,9 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
},
),
if (model.chatConfig.isShowReadingStatus &&
widget.showMessageReadRecipt &&
model.conversationType == ConvType.c2c &&
isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC)
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)
isSelf && message.status == MessageStatus.V2TIM_MSG_STATUS_SEND_SUCC &&
(message.needReadReceipt ?? false) &&
!model.isVoteMessage(widget.message))
TIMUIKitMessageReadReceipt(
messageItem: widget.message,
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(
padding: EdgeInsets.only(left: isSelf ? 0 : 16, right: isSelf ? 16 : 0),
margin: widget.padding ?? const EdgeInsets.only(bottom: 20),
@ -1177,13 +1168,9 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
Container(
margin: EdgeInsets.only(right: 12, top: 10, left: isSelf ? 16 : 0),
child: CheckBoxButton(
isChecked: model.multiSelectedMessageList.contains(message),
isChecked: model.getSelectedMessageList().contains(message),
onChanged: (value) {
if (value) {
model.addToMultiSelectedMessageList(message);
} else {
model.removeFromMultiSelectedMessageList(message);
}
model.setMessageItemChecked(message, value);
},
),
),
@ -1210,12 +1197,8 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
behavior: model.isMultiSelect ? HitTestBehavior.translucent : null,
onTap: () {
if (model.isMultiSelect) {
final checked = model.multiSelectedMessageList.contains(message);
if (checked) {
model.removeFromMultiSelectedMessageList(message);
} else {
model.addToMultiSelectedMessageList(message);
}
final checked = model.getSelectedMessageList().contains(message);
model.setMessageItemChecked(message, !checked);
} else {
return;
}
@ -1291,7 +1274,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
widget.topRowBuilder != null
? widget.topRowBuilder!(context, message)
: Container(
margin: const EdgeInsets.only(bottom: 4),
// margin: const EdgeInsets.only(bottom: 4),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 1.7),
child: Text(
@ -1309,10 +1292,7 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
maxWidth: constraints.maxWidth * 0.77,
),
child: Builder(builder: (context) {
return Column(
crossAxisAlignment: (message.isSelf ?? true) ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
GestureDetector(
return GestureDetector(
child: IgnorePointer(ignoring: model.isMultiSelect, child: _getMessageItemBuilder(message, message.status, model)),
onSecondaryTapDown: (details) {
if (widget.onLongPress != null) {
@ -1337,16 +1317,6 @@ class _TIMUIKItHistoryMessageListItemState extends TIMUIKitState<TIMUIKitHistory
onTapDown: (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),
],
),
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)
],
),
@ -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: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/tencent_cloud_chat_uikit.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/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() {
if (widget.model.chatConfig.isGroupAdminRecallEnabled) {
final selfMemberInfo =
@ -212,6 +193,15 @@ class TIMUIKitMessageTooltipState
(isDesktopScreen &&
widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE &&
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 =
model.chatConfig.isAtWhenReplyDynamic?.call(widget.message);
@ -235,7 +225,7 @@ class TIMUIKitMessageTooltipState
id: "copyMessage",
iconImageAsset: "images/copy_message.png",
onClick: () => _onTap("copyMessage", model)),
if (shouldShowForwardAction && !isVoteMessage(widget.message))
if (shouldShowForwardAction && !model.isVoteMessage(widget.message))
MessageToolTipItem(
label: TIM_t("转发"),
id: "forwardMessage",
@ -253,16 +243,17 @@ class TIMUIKitMessageTooltipState
id: "multiSelect",
iconImageAsset: "images/multi_message.png",
onClick: () => _onTap("multiSelect", model)),
MessageToolTipItem(
label: TIM_t("翻译"),
id: "translate",
iconImageAsset: "images/translate.png",
onClick: () => _onTap("translate", model)),
MessageToolTipItem(
label: TIM_t("删除"),
id: "delete",
iconImageAsset: "images/delete_message.png",
onClick: () => _onTap("delete", model)),
if (showTranslation)
MessageToolTipItem(
label: TIM_t("翻译"),
id: "translate",
iconImageAsset: "images/translate.png",
onClick: () => _onTap("translate", model)),
if (shouldShowRevokeAction)
MessageToolTipItem(
label: TIM_t("撤回"),
@ -480,10 +471,10 @@ class TIMUIKitMessageTooltipState
break;
case "multiSelect":
model.updateMultiSelectStatus(true);
model.addToMultiSelectedMessageList(widget.message);
model.setMessageItemChecked(widget.message, true);
break;
case "forwardMessage":
model.addToMultiSelectedMessageList(widget.message);
model.setMessageItemChecked(widget.message, true);
Navigator.push(
context,
MaterialPageRoute(

View File

@ -125,15 +125,18 @@ class _TIMUIKitHistoryMessageListContainerState
List<V2TimMessage?> historyMessageList = [];
Future<void> requestForData(String? lastMsgID, LoadDirection direction,
Future<bool> requestForData(String? lastMsgID, LoadDirection direction,
TUIChatSeparateViewModel model,
[int? count]) async {
if ((direction == LoadDirection.previous && model.haveMoreData) ||
[int? count, int? lastSeq]) async {
if ((direction == LoadDirection.previous) ||
(direction == LoadDirection.latest && model.haveMoreLatestData)) {
await model.loadChatRecord(
return await model.loadChatRecord(
direction: direction,
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,
initFindingMsg: widget.initFindingMsg,
messageList: messageList,
onLoadMore: (String? a, LoadDirection direction, [int? b]) async {
return await requestForData(a, direction, model, b);
onLoadMore: (String? a, LoadDirection direction, [int? b, int? lastSeq]) async {
return await requestForData(a, direction, model, b, lastSeq);
},
);
},

View File

@ -21,15 +21,25 @@ class TIMUIKitMessageReadReceipt extends TIMUIKitStatelessWidget {
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final TUIChatSeparateViewModel model =
Provider.of<TUIChatSeparateViewModel>(context, listen: false);
final TUIChatSeparateViewModel model = Provider.of<TUIChatSeparateViewModel>(context, listen: false);
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return Selector<TUIChatGlobalModel, V2TimMessageReceipt?>(
builder: (context, value, child) {
// if (value == null || value.unreadCount == 0 && value.readCount == 0) {
// return Container();
// }
if (model.conversationType == ConvType.c2c) {
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(
behavior: HitTestBehavior.opaque,
onTap: () {
@ -95,6 +105,7 @@ class TIMUIKitMessageReadReceipt extends TIMUIKitStatelessWidget {
),
),
);
}
},
selector: (c, model) {
return model.getMessageReadReceipt(messageItem.msgID ?? "");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ class HttpText extends SpecialText {
HttpText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap,
{this.start})
: super(flag, flag, textStyle, onTap: onTap);
static const String flag = '\$';
static const String flag = '!@TURL#*&\$';
final int? start;
@override
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: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/services_locatar.dart';
@ -17,9 +18,7 @@ class AtText extends StatefulWidget {
final V2TimGroupInfo? groupInfo;
final List<V2TimGroupMemberFullInfo?>? groupMemberList;
final VoidCallback? closeFunc;
final Function(
V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails)?
onChooseMember;
final Function(List<V2TimGroupMemberFullInfo> memberInfo)? onChooseMember;
final bool canAtAll;
// some Group type cant @all
@ -42,10 +41,13 @@ class AtText extends StatefulWidget {
class _AtTextState extends TIMUIKitState<AtText> {
final GroupServices _groupServices = serviceLocator<GroupServices>();
final TUISelfInfoViewModel _selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
List<V2TimGroupMemberFullInfo?>? groupMemberList;
List<V2TimGroupMemberFullInfo?>? searchMemberList;
List<V2TimGroupMemberFullInfo> selectedGroupMemberList = [];
@override
void initState() {
groupMemberList = widget.groupMemberList;
@ -58,16 +60,15 @@ class _AtTextState extends TIMUIKitState<AtText> {
super.dispose();
}
_onTapMemberItem(
V2TimGroupMemberFullInfo memberInfo, TapDownDetails? tapDetails) {
void _submitAtMemberList() {
if (widget.closeFunc != null) {
widget.closeFunc!();
}
if (widget.onChooseMember != null) {
widget.onChooseMember!(memberInfo, tapDetails);
widget.onChooseMember!(selectedGroupMemberList);
} else {
Navigator.pop(context, memberInfo);
Navigator.pop(context, selectedGroupMemberList);
}
}
@ -113,13 +114,30 @@ class _AtTextState extends TIMUIKitState<AtText> {
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
V2TimUserFullInfo? loginUserInfo = _selfInfoViewModel.loginInfo;
if (loginUserInfo != null) {
searchMemberList?.removeWhere((memberInfo) {
return memberInfo?.userID == loginUserInfo.userID;
});
}
Widget mentionedMembersBody() {
return GroupProfileMemberList(
groupType: widget.groupType ?? "",
memberList: searchMemberList ?? [],
onTapMemberItem: _onTapMemberItem,
canAtAll: widget.canAtAll,
canSelectMember: true,
canSlideDelete: false,
onSelectedMemberChange: (selectedMemberList) {
selectedGroupMemberList = selectedMemberList;
bool isAtAllSelected = selectedGroupMemberList.where((element) {
return element.userID == GroupProfileMemberList.AT_ALL_USER_ID;
}).isNotEmpty;
if (isAtAllSelected) {
_submitAtMemberList();
}
},
touchBottomCallBack: () {
// Get all by once, unnecessary to load more
},
@ -168,6 +186,20 @@ class _AtTextState extends TIMUIKitState<AtText> {
fontSize: 17,
),
),
actions: [
TextButton(
onPressed: () {
_submitAtMemberList();
},
child: Text(
TIM_t("确定"),
style: TextStyle(
color: theme.appbarTextColor,
fontSize: 14,
),
),
)
],
),
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';
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 showCameraAction;
final bool showFilePickAction;
@ -316,20 +320,19 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
}).toList();
}
_sendVideoMessage(AssetEntity asset, TUIChatSeparateViewModel model) async {
final plugin = FcNativeVideoThumbnail();
final originFile = await asset.originFile;
final size = await originFile!.length();
if (size >= 104857600) {
_sendVideoMessage(AssetEntity asset, int size, TUIChatSeparateViewModel model) async {
if (size >= MorePanelConfig.VIDEO_MAX_SIZE) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("发送失败,视频不能大于100MB"),
infoCode: 6660405));
infoRecommendText: TIM_t("文件大小超出了限制")));
return;
}
final plugin = FcNativeVideoThumbnail();
final originFile = await asset.originFile;
final duration = asset.videoDuration.inSeconds;
final filePath = originFile.path;
final filePath = originFile!.path;
final convID = widget.conversationID;
final convType = widget.conversationType;
@ -341,9 +344,9 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
srcFile: originFile.path,
destFile: tempPath,
format: 'jpeg',
width: 128,
width: 1280,
quality: 100,
height: 128,
height: 1280,
);
MessageUtils.handleMessageError(
model.sendVideoMessage(
@ -407,8 +410,16 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
final originFile = await asset.originFile;
final filePath = originFile?.path;
final type = asset.type;
final size = await originFile!.length();
if (filePath != null) {
if (type == AssetType.image) {
if (size >= MorePanelConfig.IMAGE_MAX_SIZE) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("文件大小超出了限制")));
return;
}
MessageUtils.handleMessageError(
model.sendImageMessage(
imagePath: filePath,
@ -418,7 +429,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
}
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;
if (originFile != null) {
final type = pickedFile!.type;
final size = await originFile!.length();
if (type == AssetType.image) {
if (size >= MorePanelConfig.IMAGE_MAX_SIZE) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO,
infoRecommendText: TIM_t("文件大小超出了限制")));
return;
}
MessageUtils.handleMessageError(
model.sendImageMessage(
imagePath: originFile.path,
@ -489,7 +508,7 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
context);
}
if (type == AssetType.video) {
_sendVideoMessage(pickedFile, model);
_sendVideoMessage(pickedFile, size, model);
}
} else {
// Toast.showToast(ToastType.fail, TIM_t("图片不能为空"), context);
@ -591,6 +610,13 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
File file = File(result.files.single.path!);
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;
MessageUtils.handleMessageError(
@ -643,14 +669,22 @@ class _MorePanelState extends TIMUIKitState<MorePanel> {
_goToVideoUI(String type) async {
if (!PlatformUtils().isWeb) {
final hasCameraPermission = type == TYPE_VIDEO
? await Permissions.checkPermission(context, Permission.camera.value)
: true;
final hasMicphonePermission = await Permissions.checkPermission(
bool hasCameraPermission = false;
bool hasMicrophonePermission = false;
if (type == TYPE_VIDEO) {
hasCameraPermission = await Permissions.checkPermission(context, Permission.camera.value);
hasMicrophonePermission = await Permissions.checkPermission(
context, Permission.microphone.value);
if (!hasCameraPermission || !hasMicphonePermission) {
if (!hasCameraPermission || !hasMicrophonePermission) {
return;
}
} else {
hasMicrophonePermission = await Permissions.checkPermission(
context, Permission.microphone.value);
if (!hasMicrophonePermission) {
return;
}
}
}
final isGroup = widget.conversationType == ConvType.group;

View File

@ -192,7 +192,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
return stickerPackageList;
}
setCurrentCursor(int? value) {
_setCurrentCursor(int? value) {
currentCursor = value;
}
@ -203,7 +203,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
return emojiRegex().hasMatch(input);
}
void deleteStickerFromText() {
void _deleteStickerFromText() {
String originalText = textEditingController.text;
if (originalText == zeroWidthSpace) {
@ -239,9 +239,21 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
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;
if (currentCursor != null && currentCursor! > -1 && currentCursor! < currentText.length + 1) {
final firstString = currentText.substring(0, currentCursor);
@ -267,13 +279,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
String text = textEditingController.text;
String convID = id ?? widget.conversationID;
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);
return await conversationModel.setConversationDraft(groupID: groupID ?? widget.groupID, isTopic: isTopic, isAllowWeb: widget.model.chatConfig.isUseDraftOnWeb, conversationID: conversationID, draftText: draftText);
}
// onSubmitted一样
onEmojiSubmitted() {
_onEmojiSubmitted() {
lastText = "";
final text = textEditingController.text.trim();
final convType = widget.conversationType;
@ -304,7 +316,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
}
// index为emoji的index,data为baseurl+name
onCustomEmojiFaceSubmitted(int index, String data) {
_onCustomEmojiFaceSubmitted(int index, String data) {
final convType = widget.conversationType;
// 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;
}
} else if (textLength > 0 && text[textLength - 1] == "@" && lastText.length < textLength) {
V2TimGroupMemberFullInfo? memberInfo = await Navigator.push(
List<V2TimGroupMemberFullInfo> selectedAtMemberList = await Navigator.push(
context,
MaterialPageRoute(
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);
if (memberInfo != null) {
mentionedMembersMap["@$showName"] = memberInfo;
textEditingController.text = "$text$showName ";
lastText = "$text$showName ";
String addAtCharacter = i == 0 ? "" : "@";
textEditingController.text = "${textEditingController.text}$addAtCharacter$showName ";
lastText = "${textEditingController.text}$addAtCharacter$showName ";
}
}
}
lastText = textEditingController.text;
}
@ -858,12 +876,13 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
context: context,
defaultWidget: TIMUIKitTextFieldLayoutNarrow(
stickerPackageList: stickerPackageList,
onEmojiSubmitted: onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
backSpaceText: deleteStickerFromText,
addStickerToText: addStickerToText,
onEmojiSubmitted: _onEmojiSubmitted,
onCustomEmojiFaceSubmitted: _onCustomEmojiFaceSubmitted,
backSpaceText: _deleteStickerFromText,
addStickerToText: _addStickerToText,
customStickerPanel: widget.customStickerPanel,
onChanged: widget.onChanged,
onDeleteText: _onDeleteText,
backgroundColor: widget.backgroundColor,
morePanelConfig: widget.morePanelConfig,
repliedMessage: value,
@ -876,7 +895,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
conversationType: widget.conversationType,
focusNode: focusNode,
controller: widget.controller,
setCurrentCursor: setCurrentCursor,
setCurrentCursor: _setCurrentCursor,
onCursorChange: _onCursorChange,
model: model,
handleSendEditStatus: _handleSendEditStatus,
@ -895,10 +914,10 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
chatConfig: widget.chatConfig ?? widget.model.chatConfig,
theme: theme,
currentConversation: widget.currentConversation,
onEmojiSubmitted: onEmojiSubmitted,
onCustomEmojiFaceSubmitted: onCustomEmojiFaceSubmitted,
backSpaceText: deleteStickerFromText,
addStickerToText: addStickerToText,
onEmojiSubmitted: _onEmojiSubmitted,
onCustomEmojiFaceSubmitted: _onCustomEmojiFaceSubmitted,
backSpaceText: _deleteStickerFromText,
addStickerToText: _addStickerToText,
customStickerPanel: widget.customStickerPanel,
onChanged: widget.onChanged,
backgroundColor: widget.backgroundColor,
@ -913,7 +932,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
conversationType: widget.conversationType,
focusNode: focusNode,
controller: widget.controller,
setCurrentCursor: setCurrentCursor,
setCurrentCursor: _setCurrentCursor,
onCursorChange: _onCursorChange,
model: model,
handleSendEditStatus: _handleSendEditStatus,

View File

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:extended_text_field/extended_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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_state.dart';
@ -81,6 +82,8 @@ class TIMUIKitTextFieldLayoutNarrow extends StatefulWidget {
final V2TimMessage? repliedMessage;
final void Function(String)? onDeleteText;
/// show send emoji icon
final bool showSendEmoji;
@ -112,6 +115,7 @@ class TIMUIKitTextFieldLayoutNarrow extends StatefulWidget {
required this.model,
this.backgroundColor,
this.onChanged,
this.onDeleteText,
required this.handleSendEditStatus,
required this.handleAtText,
required this.handleSoftKeyBoardDelete,
@ -444,7 +448,10 @@ class _TIMUIKitTextFieldLayoutNarrowState extends TIMUIKitState<TIMUIKitTextFiel
Expanded(
child: showSendSoundText
? SendSoundMessage(onDownBottom: widget.goDownBottom, conversationID: widget.conversationID, conversationType: widget.conversationType)
: KeyboardVisibility(
: Stack(
children: [
Center(
child: KeyboardVisibility(
child: ExtendedTextField(
maxLines: 4,
minLines: 1,
@ -490,6 +497,7 @@ class _TIMUIKitTextFieldLayoutNarrowState extends TIMUIKitState<TIMUIKitTextFiel
isUseTencentCloudChatPackage: widget.model.chatConfig.stickerPanelConfig?.useTencentCloudChatStickerPackage ?? true,
customEmojiStickerList: widget.customEmojiStickerList,
showAtBackground: true,
checkHttpLink: false,
)),
onChanged: (bool 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(
width: 10,
),

View File

@ -66,10 +66,12 @@ class TIMUIKitChatConfig {
/// Control if allowed to show reading status for group.
/// [Default]: true.
/// [Deprecated: ] Please use [isShowReadingStatus] instead.
final bool isShowGroupReadingStatus;
/// Control if allowed to report reading status for group.
/// [Default]: true.
/// [Deprecated: ] Please use [isShowReadingStatus] instead.
final bool isReportGroupReadingStatus;
/// 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;
/// The main switch of the group read receipt.
/// [Deprecated: ] Please use [isShowReadingStatus] instead.
final bool isShowGroupMessageReadReceipt;
/// [Deprecated: ] Please use [groupReadReceiptPermissionList] instead.
/// [Deprecated: ] not support.
final List<GroupReceptAllowType>? groupReadReceiptPermisionList;
/// Control which group can send message read receipt.
/// [Deprecated: ] not support.
final List<GroupReceiptAllowType>? groupReadReceiptPermissionList;
/// Control if show self name in group chat.
@ -271,7 +275,7 @@ class TIMUIKitChatConfig {
this.isShowSelfNameInGroup = false,
this.isAtWhenReplyDynamic,
this.offlinePushInfo,
@Deprecated("Please use [isShowGroupReadingStatus] instead")
@Deprecated("Please use [isShowReadingStatus] instead")
this.isShowGroupMessageReadReceipt = true,
this.upperRecallTime = 120,
this.isShowOthersNameInGroup = true,
@ -281,8 +285,9 @@ class TIMUIKitChatConfig {
this.notificationTitle = "",
this.notificationIOSSound = "",
this.isAllowSoundMessage = true,
@Deprecated("Please use [groupReadReceiptPermissionList] instead")
@Deprecated("not support")
this.groupReadReceiptPermisionList,
@Deprecated("not support")
this.groupReadReceiptPermissionList,
this.isAllowEmojiPanel = true,
this.isAllowShowMorePanel = true,
@ -294,6 +299,7 @@ class TIMUIKitChatConfig {
this.isEnableTextSelection,
this.additionalDesktopMessageHoverBarItem,
this.isShowGroupReadingStatus = true,
@Deprecated("Please use [isShowReadingStatus] instead")
this.isReportGroupReadingStatus = true,
this.showC2cMessageEditStatus = true,
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';
class MultiSelectPanel extends TIMUIKitStatelessWidget {
final int forwardMsgNumLimit = 30;
final ConvType conversationType;
MultiSelectPanel({Key? key, required this.conversationType})
@ -21,6 +23,39 @@ class MultiSelectPanel extends TIMUIKitStatelessWidget {
_handleForwardMessage(BuildContext context, bool isMergerForward,
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(
context,
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,
TUIChatSeparateViewModel model) {
TUIKitWidePopup.showPopupWindow(

View File

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

View File

@ -31,14 +31,8 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
final int? convType;
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({
Key? key,
required this.isShowDraft,
required this.faceUrl,
required this.nickName,
required this.lastMsg,
@ -56,22 +50,20 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
Widget _getShowMsgWidget(BuildContext context) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
if (isShowDraft && draftText != null && draftText != "") {
return TIMUIKitDraftText(
context: context,
draftText: draftText ?? "",
fontSize: isDesktopScreen ? 12 : 14,
);
} else if (lastMsg != null) {
if (lastMessageBuilder != null &&
if (lastMsg != null && lastMessageBuilder != null &&
lastMessageBuilder!(lastMsg, groupAtInfoList) != null) {
return lastMessageBuilder!(lastMsg, groupAtInfoList)!;
}
if (lastMsg != null || (draftText != null && draftText != "")) {
return TIMUIKitLastMsg(
fontSize: isDesktopScreen ? 12 : 14,
groupAtInfoList: groupAtInfoList,
lastMsg: lastMsg,
isDisturb: isDisturb,
unreadCount: unreadCount,
context: context,
draftText: draftText ?? "",
);
}
@ -81,8 +73,7 @@ class TIMUIKitConversationItem extends TIMUIKitStatelessWidget {
}
bool isHaveSecondLine() {
return (isShowDraft && draftText != null && draftText != "") ||
(lastMsg != null);
return (draftText != null && draftText != "") || (lastMsg != null);
}
Widget _getTimeStringForChatWidget(BuildContext context, TUITheme theme) {

View File

@ -2,12 +2,16 @@
import 'dart:convert';
import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
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/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';
class TIMUIKitLastMsg extends StatefulWidget {
@ -15,8 +19,20 @@ class TIMUIKitLastMsg extends StatefulWidget {
final List<V2TimGroupAtInfo?> groupAtInfoList;
final BuildContext context;
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
State<TIMUIKitLastMsg> createState() => _TIMUIKitLastMsgState();
@ -34,7 +50,10 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
@override
void didUpdateWidget(covariant TIMUIKitLastMsg 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();
}
}
@ -63,22 +82,33 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
final isAdminRevoke = revokeStatus.$2;
if (isRevokedMessage) {
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) {
setState(() {
groupTipsAbstractText = TIM_t_para("{{option1}}撤回了一条消息", "$option1撤回了一条消息")(option1: option1);
});
}
} else {
final newText = await _getLastMsgShowText(widget.lastMsg, widget.context) ?? "";
String msgShowText = await _getLastMsgShowText(widget.lastMsg, widget.context) ?? "";
if (mounted) {
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 {
final msgType = message!.elemType;
switch (msgType) {
@ -121,37 +151,87 @@ class _TIMUIKitLastMsgState extends TIMUIKitState<TIMUIKitLastMsg> {
}
String _getAtMessage() {
bool atMe = false;
bool atAll = false;
String msg = "";
for (var item in widget.groupAtInfoList) {
if (item!.atType == 1) {
msg = TIM_t("[有人@我] ");
} else {
atMe = true;
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("[@所有人]");
} else if (atMe) {
msg = TIM_t("[有人@我]");
}
}
return msg;
}
String _getDraftShowText() {
final draftShowText = TIM_t("草稿");
return '[$draftShowText]';
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final TUITheme theme = value.theme;
final icon = _getIconByMsgStatus(context);
String disturbUnreadCountInfo = _getDisturbUnreadCountInfo();
return Row(children: [
if (icon != null)
Container(
margin: const EdgeInsets.only(right: 2),
child: icon,
),
if (widget.groupAtInfoList.isNotEmpty) Text(_getAtMessage(), style: TextStyle(color: theme.cautionColor, fontSize: widget.fontSize)),
if (TencentUtils.checkString(groupTipsAbstractText) != null)
if (widget.groupAtInfoList.isNotEmpty)
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(
child: Text(
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,
)
),
),
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,
removeMember: _kickedOffMember,
touchBottomCallBack: () {},
onTapMemberItem: (friendInfo, details) {
onTapMemberItem: (memberInfo, details) {
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,
removeMember: _kickedOffMember,
touchBottomCallBack: () {},
onTapMemberItem: (friendInfo, details) {
onTapMemberItem: (memberInfo, details) {
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,
/// 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(
{Key? key,
@ -154,6 +154,10 @@ class _TIMUIKitGroupProfileState extends TIMUIKitState<TIMUIKitGroupProfile> {
groupListenerModel.needUpdate = null;
switch (needUpdate.updateType) {
case UpdateType.groupInfo:
if (needUpdate.groupInfoSubType == GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER) {
model.onOwnerChanged(needUpdate.ownerID);
}
model.loadGroupInfo(widget.groupID);
break;
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_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_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_name_card.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() {
return GroupMemberTile();
return GroupMemberTitle();
}
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_im_base/tencent_im_base.dart';
class GroupMemberTile extends TIMUIKitStatelessWidget {
GroupMemberTile({
class GroupMemberTitle extends TIMUIKitStatelessWidget {
GroupMemberTitle({
Key? key,
}) : super(key: key);
@ -44,7 +44,7 @@ class GroupMemberTile extends TIMUIKitStatelessWidget {
return InkWell(
onTapDown: (details) {
if (model.onClickUser != null && element?.userID != null) {
model.onClickUser!(element!.userID, details);
model.onClickUser!(element!, details);
}
},
child: SizedBox(

View File

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

View File

@ -240,51 +240,14 @@ class _TIMUIKitProfileState extends TIMUIKitState<TIMUIKitProfile> {
}
void handleAddFriend() async {
model.addFriend(userInfo.userID).then((res) {
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));
});
model.addFriend(userInfo.userID);
}
void handleDeleteFriend() {
model.deleteFriend(userInfo.userID).then((res) {
if (res == null) {
throw Error();
Future<void> handleDeleteFriend() async {
var result = await model.deleteFriend(userInfo.userID, needUpdateData: false);
if (result != null) {
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) {

View File

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

View File

@ -66,16 +66,24 @@ class TIMUIKitSearchFriendState extends TIMUIKitState<TIMUIKitSearchFriend> {
int convIndex = _conversationList
.indexWhere((item) => conv.friendInfo?.userID == item?.userID);
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(
onClick: () {
widget.onTapConversation(conversation, null);
},
faceUrl: conv.friendInfo?.userProfile?.faceUrl ?? "",
showName: "",
lineOne: conversation.showName ??
conversation.userID ??
conv.friendInfo?.userID ??
"",
lineOne: conversation.userID!,
lineTwo: TIM_t("昵称") + ":" + showNickName!,
);
}).toList(),
_renderShowALl(filteredFriendResultList.length),

View File

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.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/business_logic/separate_models/tui_chat_separate_view_model.dart';
@ -53,7 +54,7 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
}
List<String> _getAbstractList() {
return widget.model.multiSelectedMessageList.map((e) {
return widget.model.getSelectedMessageList().map((e) {
final sender = (e.nickName != null && e.nickName!.isNotEmpty)
? e.nickName
: e.sender;
@ -62,6 +63,11 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
}
handleForwardMessage() async {
var confirmResult = await _showConfirmForwardDialog(context);
if (confirmResult == null) {
return;
}
if (widget.isMergerForward) {
await widget.model.sendMergerMessage(
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
void dispose() {
super.dispose();

View File

@ -184,7 +184,8 @@ class GesturedImageState extends ExtendedImageGestureState {
gestureDetails = GestureDetails(
totalScale: _gestureConfig!.initialScale,
offset: Offset.zero,
)..initialAlignment = _gestureConfig!.initialAlignment;
initialAlignment: _gestureConfig!.initialAlignment,
);
}
@override
@ -433,7 +434,8 @@ class GesturedImageState extends ExtendedImageGestureState {
_gestureDetails = GestureDetails(
totalScale: _gestureConfig!.initialScale,
offset: Offset.zero,
)..initialAlignment = _gestureConfig!.initialAlignment;
initialAlignment: _gestureConfig!.initialAlignment,
);
}
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';
class GroupProfileMemberList extends StatefulWidget {
static String AT_ALL_USER_ID = "__kImSDK_MesssageAtALL__";
final List<V2TimGroupMemberFullInfo?> memberList;
final Function(String userID)? removeMember;
final bool canSlideDelete;
@ -53,7 +54,7 @@ class GroupProfileMemberList extends StatefulWidget {
}
class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList> {
List<V2TimGroupMemberFullInfo> selectedMember = [];
List<V2TimGroupMemberFullInfo> selectedMemberList = [];
_getShowName(V2TimGroupMemberFullInfo? item) {
final friendRemark = item?.friendRemark ?? "";
@ -93,7 +94,7 @@ class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList>
if (widget.canAtAll) {
final canAtGroupType = ["Work", "Public", "Meeting"];
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(
onChanged: (isChecked) {
if (isChecked) {
if (widget.maxSelectNum != null && selectedMember.length >= widget.maxSelectNum!) {
if (widget.maxSelectNum != null && selectedMemberList.length >= widget.maxSelectNum!) {
return;
}
selectedMember.add(memberInfo);
selectedMemberList.add(memberInfo);
} else {
selectedMember.remove(memberInfo);
selectedMemberList.removeWhere((element) => element.userID == memberInfo.userID);
}
if (widget.onSelectedMemberChange != null) {
widget.onSelectedMemberChange!(selectedMember);
widget.onSelectedMemberChange!(selectedMemberList);
}
setState(() {});
},
isChecked: selectedMember.contains(memberInfo)),
isChecked: selectedMemberList.where((element) => element.userID == memberInfo.userID).toList().isNotEmpty
),
),
Container(
width: isDesktopScreen ? 30 : 36,
@ -194,17 +197,17 @@ class _GroupProfileMemberListState extends TIMUIKitState<GroupProfileMemberList>
widget.onTapMemberItem!(memberInfo, null);
}
if (widget.canSelectMember) {
final isChecked = selectedMember.contains(memberInfo);
final isChecked = selectedMemberList.contains(memberInfo);
if (isChecked) {
selectedMember.remove(memberInfo);
selectedMemberList.remove(memberInfo);
} else {
if (widget.maxSelectNum != null && selectedMember.length >= widget.maxSelectNum!) {
if (widget.maxSelectNum != null && selectedMemberList.length >= widget.maxSelectNum!) {
return;
}
selectedMember.add(memberInfo);
selectedMemberList.add(memberInfo);
}
if (widget.onSelectedMemberChange != null) {
widget.onSelectedMemberChange!(selectedMember);
widget.onSelectedMemberChange!(selectedMemberList);
}
setState(() {});
}

View File

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

View File

@ -5,6 +5,7 @@ import 'package:extended_text/extended_text.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.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_im_base/base_widgets/tim_stateless_widget.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)) {
contentData += '\$' + c + '\$';
contentData += HttpText.flag + c + HttpText.flag;
_contentList.add(TextSpan(
text: c,
style: TextStyle(color: LinkUtils.hexToColor("015fff")),
@ -160,12 +161,12 @@ class LinkText extends TIMStatelessWidget {
Widget timBuild(BuildContext context) {
return ExtendedText(_getContentSpan(messageText, context), softWrap: true,
onSpecialTextTap: (dynamic parameter) {
if (parameter.toString().startsWith('\$')) {
if (parameter.toString().startsWith(HttpText.flag)) {
if (onLinkTap != null) {
onLinkTap!((parameter.toString()).replaceAll('\$', ''));
onLinkTap!((parameter.toString()).replaceAll(HttpText.flag, ''));
} else {
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();
WidgetsBinding.instance.addPostFrameCallback((_) {
double w = getVideoWidth();
double h = getVideoHeight();
double aspectRatio = player.value.aspectRatio;
ChewieController controller = ChewieController(
videoPlayerController: player,
autoPlay: true,
looping: false,
showControlsOnInitialize: false,
allowPlaybackSpeedChanging: false,
aspectRatio: w == 0 || h == 0 ? null : w / h,
aspectRatio: aspectRatio,
customControls: VideoCustomControls(downloadFn: () async {
return await _saveVideo();
}));

View File

@ -1,6 +1,6 @@
name: tencent_cloud_chat_uikit
description: A powerful chat UI component library and business logic for Tencent Cloud Chat, creating seamless in-app chat modules for delightful user experiences.
version: 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
repository: https://github.com/TencentCloud/chat-uikit-flutter
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
@ -21,17 +21,17 @@ dependencies:
sdk: flutter
adaptive_action_sheet: ^2.0.1
provider: ^6.0.1
intl: any
intl: ^0.19.0
get_it: ^7.2.0
dotted_border: ^2.0.0+2
flutter_svg: ^2.0.6
image_picker: ^0.8.5+3
file_picker: ^5.3.0
tencent_super_tooltip: ^0.0.1
video_player: ^2.7.0
chewie: ^1.7.5
video_player: ^2.9.0
chewie: ^1.8.5
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
lpinyin: ^2.0.3
transparent_image: ^2.0.0
@ -40,13 +40,13 @@ dependencies:
cached_network_image: ^3.3.0
shared_preferences: ^2.0.13
scroll_to_index: ^2.1.1
wechat_assets_picker: ^8.9.0-dev.1
wechat_camera_picker: ^4.2.0-dev.2
wechat_assets_picker: ^9.3.3
wechat_camera_picker: ^4.3.4
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: ^14.0.0
package_info_plus: ^4.0.1
package_info_plus: ^8.0.0
loading_animation_widget: ^1.1.0+3
permission_handler: ^10.2.0
tuple: ^2.0.0
@ -63,19 +63,20 @@ dependencies:
tencent_keyboard_visibility: ^1.0.1
tim_ui_kit_sticker_plugin: ^3.2.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
tencent_cloud_uikit_core: ^1.6.0
pasteboard: ^0.2.0
desktop_drop: ^0.4.4
device_info_plus: any
device_info_plus: ^10.1.2
cross_file: ^0.3.3+4
csslib: 0.17.2
csslib: ^0.17.2
diff_match_patch: ^0.4.1
just_audio: ^0.9.34
markdown: ^7.1.0
logger: ^2.0.1
image_clipboard: ^1.0.0+2
visibility_detector: ^0.4.0+2
dev_dependencies:
flutter_lints: ^1.0.0