This commit is contained in:
2025-06-30 16:24:20 +08:00
commit 68de5eb048
454 changed files with 56921 additions and 0 deletions
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
enum TIMCallbackType { API_ERROR, FLUTTER_ERROR, INFO }
class TIMCallback {
TIMCallbackType? type;
String? errorMsg;
int? errorCode;
StackTrace? stackTrace;
Object? catchError;
int? infoCode;
String? infoRecommendText;
TIMCallback(
{this.catchError,
this.infoRecommendText,
this.errorMsg,
this.errorCode,
this.stackTrace,
this.infoCode,
this.type});
}
+51
View File
@@ -0,0 +1,51 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
abstract class TIMState<T extends StatefulWidget> extends State<T> {
@override
initState() {
super.initState();
}
// override这个函数来承接处理onTIMCallback回调。
// 目前base层,会自动触发`TIMCallbackType.FLUTTER_ERROR`类型Flutter Framework异常。
// 其他类型callback请自行处理。
void onTIMCallback(TIMCallback callbackValue) {
// TODO: 这里后续看看要不要默认加上报逻辑。
}
bool isAndroidDevice(){
return !kIsWeb && Platform.isAndroid;
}
bool isIosDevice(){
return !kIsWeb && Platform.isIOS;
}
bool isWebDevice(){
return kIsWeb;
}
setTIMState(VoidCallback fn){
}
@override
Widget build(BuildContext context) {
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
onTIMCallback(TIMCallback(
type: TIMCallbackType.FLUTTER_ERROR,
stackTrace: details.stack,
errorMsg: "Error from Flutter"));
};
return timBuild(context);
}
// 请override这个方法来render界面
Widget timBuild(BuildContext context);
}
@@ -0,0 +1,43 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
abstract class TIMStatelessWidget extends StatelessWidget {
const TIMStatelessWidget({Key? key}) : super(key: key);
// override这个函数来承接处理onTIMCallback回调。
// 目前base层,会自动触发`TIMCallbackType.FLUTTER_ERROR`类型Flutter Framework异常。
// 其他类型callback请自行处理。
void onTIMCallback(TIMCallback callbackValue) {
}
bool isAndroidDevice(){
return !kIsWeb && Platform.isAndroid;
}
bool isIosDevice(){
return !kIsWeb && Platform.isIOS;
}
bool isWebDevice(){
return kIsWeb;
}
@override
Widget build(BuildContext context) {
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
onTIMCallback(TIMCallback(
type: TIMCallbackType.FLUTTER_ERROR,
stackTrace: details.stack,
errorMsg: "Error from Flutter"));
};
return timBuild(context);
}
// 请override这个方法来render界面
Widget timBuild(BuildContext context);
}
+8
View File
@@ -0,0 +1,8 @@
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TUIKitBuildValue {
// 这里预留以后可以扩展全局通用能力
TUITheme theme;
TUIKitBuildValue({required this.theme});
}
+12
View File
@@ -0,0 +1,12 @@
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class TIMUIKitClass {
static final CoreServicesImpl _coreServices =
serviceLocator<CoreServicesImpl>();
static void onTIMCallback(TIMCallback callbackValue) {
_coreServices.callOnCallback(callbackValue);
}
}
+39
View File
@@ -0,0 +1,39 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_state.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme_view_model.dart';
class TIMUIKitState<T extends StatefulWidget> extends TIMState<T> {
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
@override
void onTIMCallback(TIMCallback callbackValue) {
super.onTIMCallback(callbackValue);
_coreServices.callOnCallback(callbackValue);
}
@override
Widget timBuild(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: serviceLocator<TUIThemeViewModel>()),
],
builder: (BuildContext context, Widget? w) {
final theme = Provider.of<TUIThemeViewModel>(context).theme;
final value = TUIKitBuildValue(theme: theme);
return tuiBuild(context, value);
},
);
}
@required
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return Container();
}
}
@@ -0,0 +1,40 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_stateless_widget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme_view_model.dart';
class TIMUIKitStatelessWidget extends TIMStatelessWidget {
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
TIMUIKitStatelessWidget({Key? key}) : super(key: key);
@override
void onTIMCallback(TIMCallback callbackValue) {
super.onTIMCallback(callbackValue);
_coreServices.callOnCallback(callbackValue);
}
@override
Widget timBuild(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: serviceLocator<TUIThemeViewModel>()),
],
builder: (BuildContext context, Widget? w) {
final theme = Provider.of<TUIThemeViewModel>(context).theme;
final value = TUIKitBuildValue(theme: theme);
return tuiBuild(context, value);
},
);
}
@required
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return Container();
}
}
BIN
View File
Binary file not shown.
@@ -0,0 +1,15 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class AddFriendLifeCycle {
/// Before requesting to add a user as friend or a contact,
/// `true` means can add continually, while `false` will not add.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(
String userID, String? remark, String? friendGroup, String? addWording,
[BuildContext? context]) shouldAddFriend;
AddFriendLifeCycle({
this.shouldAddFriend = DefaultLifeCycle.defaultAddFriend,
});
}
@@ -0,0 +1,14 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class AddGroupLifeCycle {
/// Before requesting to add or join to a group,
/// `true` means can add continually, while `false` will not add.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String groupID, String message, [BuildContext? context])
shouldAddGroup;
AddGroupLifeCycle({
this.shouldAddGroup = DefaultLifeCycle.defaultAddGroup,
});
}
@@ -0,0 +1,89 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_class.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
typedef MessageFunction = Future<V2TimMessage?> Function(V2TimMessage message);
typedef MessageFunctionNullCallback = Function(V2TimValueCallback<V2TimMessage> res);
typedef MessageFunctionOptional = Future<V2TimMessage?> Function(V2TimMessage message);
typedef MessageListFunction = Future<List<V2TimMessage>> Function(List<V2TimMessage> messageList);
typedef MessageListFunctionAsync = List<V2TimMessage> Function(List<V2TimMessage> messageList);
typedef FutureBool = Future<bool>;
typedef AddFriendFunction = Function(String userID, String? remark, String? friendGroup, String? addWording);
typedef ConversationListFunction = Future<List<V2TimConversation?>> Function(List<V2TimConversation?> conversationList);
typedef FriendListFunction = Future<List<V2TimFriendInfo>> Function(List<V2TimFriendInfo> friendList);
typedef FriendInfoFunction = Future<V2TimFriendInfo?> Function(V2TimFriendInfo? friendInfo);
/// Here is the default life cycle hooks implementation for all the hooks in TUIKit.
abstract class DefaultLifeCycle {
static Future<List<V2TimConversation?>> defaultConversationListSolution(List<V2TimConversation?> list) async {
return list;
}
static Future<List<V2TimFriendInfo>> defaultFriendListSolution(List<V2TimFriendInfo> list) async {
return list;
}
static Future<V2TimMessage> defaultMessageSolution(V2TimMessage message) async {
return message;
}
static Future<V2TimMessage> defaultTwoMessagesSolution(V2TimMessage message, [V2TimMessage? repliedMessage]) async {
return message;
}
static Future<List<V2TimMessage>> defaultMessageListSolution(List<V2TimMessage> list) async {
return list;
}
static List<V2TimMessage> defaultMessageListSolutionAsync(List<V2TimMessage> list) {
return list;
}
static Future<bool> defaultAsyncBooleanSolution(dynamic) async {
return true;
}
static bool defaultBooleanSolution(dynamic) {
return true;
}
static defaultNullCallbackSolution(dynamic) {}
static Future<bool> defaultAddFriend(String userID, String? remark, String? friendGroup, String? addWording,
[BuildContext? context]) async {
return true;
}
static Future<bool> defaultAddGroup(String groupID, String message, [BuildContext? context]) async {
return true;
}
static Future<V2TimFriendInfo?> defaultFriendInfoSolution(V2TimFriendInfo? friendInfo) async {
return friendInfo;
}
static Future<void> defaultPopBackRemind() async {
// You have to implement the exact life cycle hook in this case.
TIMUIKitClass.onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO, infoRecommendText: TIM_t("请传入离开群组生命周期函数,提供返回首页或其他页面的导航方法。"), infoCode: 6661402));
return;
}
}
@@ -0,0 +1,12 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class BlockListLifeCycle {
/// Before requesting to delete a user from block list,
/// `true` means can delete continually, while `false` will not delete.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(List<String> userIDList) shouldDeleteFromBlockList;
BlockListLifeCycle({
this.shouldDeleteFromBlockList = DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}
@@ -0,0 +1,54 @@
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class ChatLifeCycle {
/// Before a new message will be added to historical message list from long connection.
/// You may not render this message by return null.
MessageFunctionOptional newMessageWillMount;
/// Before a modified message updated to historical message list UI.
MessageFunction modifiedMessageWillMount;
/// Before a new message will be sent.
/// Returns null can block the message from sending.
// Future<V2TimMessage?> Function(V2TimMessage message, [V2TimMessage? repliedMessage]) messageWillSend;
/// After a new message been sent.
MessageFunctionNullCallback messageDidSend;
/// After getting the latest message list from API,
/// and before historical message list will be rendered.
/// You may add or delete some messages here.
MessageListFunction didGetHistoricalMessageList;
/// Before deleting a message from historical message list,
/// `true` means can delete continually, while `false` will not delete.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String msgID) shouldDeleteMessage;
/// Before clearing the historical message list,
/// `true` means can clear continually, while `false` will not clear.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String conversationID) shouldClearHistoricalMessageList;
/// Before rendering a message to message list.
bool Function(V2TimMessage msg) messageShouldMount;
/// Before all message will be rendered on the message list.
/// You may add or delete some messages here.
MessageListFunctionAsync messageListShouldMount;
ChatLifeCycle({
this.shouldClearHistoricalMessageList = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteMessage = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.messageDidSend = DefaultLifeCycle.defaultNullCallbackSolution,
this.didGetHistoricalMessageList = DefaultLifeCycle.defaultMessageListSolution,
// this.messageWillSend = DefaultLifeCycle.defaultTwoMessagesSolution,
this.modifiedMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
this.newMessageWillMount = DefaultLifeCycle.defaultMessageSolution,
this.messageShouldMount = DefaultLifeCycle.defaultBooleanSolution,
this.messageListShouldMount = DefaultLifeCycle.defaultMessageListSolutionAsync,
});
}
@@ -0,0 +1,25 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class ConversationLifeCycle {
/// Before deleting a conversation, or a channel, from the conversation list,
/// `true` means can delete continually, while `false` will not delete.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String conversationID) shouldDeleteConversation;
/// Before clearing the historical message for a specific conversation, provided in parameter,
/// `true` means can clear continually, while `false` will not clear.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String conversationID)
shouldClearHistoricalMessageForConversation;
/// Before conversation list will mount or update to conversation page.
ConversationListFunction conversationListWillMount;
ConversationLifeCycle({
this.conversationListWillMount =
DefaultLifeCycle.defaultConversationListSolution,
this.shouldClearHistoricalMessageForConversation =
DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteConversation = DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}
@@ -0,0 +1,10 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class FriendListLifeCycle {
/// Before friend list (contacts list) will mount or update to contacts page.
FriendListFunction friendListWillMount;
FriendListLifeCycle({
this.friendListWillMount = DefaultLifeCycle.defaultFriendListSolution,
});
}
@@ -0,0 +1,11 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class GroupProfileLifeCycle {
/// In this case, you have better navigating to you home page or conversation list page,
/// due to user request to leave the group, as quitting or disbanding.
Future<void> Function() didLeaveGroup;
GroupProfileLifeCycle({
this.didLeaveGroup = DefaultLifeCycle.defaultPopBackRemind,
});
}
@@ -0,0 +1,20 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class NewContactLifeCycle {
/// Before accepting a friend or contact requirement from other user,
/// `true` means can accept continually, while `false` will not accept.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String userID) shouldAcceptContactApplication;
/// Before refusing a friend or contact requirement from other user,
/// `true` means can refuse continually, while `false` will not refuse.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String userID) shouldRefuseContactApplication;
NewContactLifeCycle({
this.shouldAcceptContactApplication =
DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldRefuseContactApplication =
DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}
@@ -0,0 +1,33 @@
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/base_life_cycle.dart';
class ProfileLifeCycle {
/// Before adding a contact to block list,
/// `true` means can add continually, while `false` will not add.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String userID) shouldAddToBlockList;
/// Before deleting a contact or friend,
/// `true` means can delete continually, while `false` will not delete.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String userID) shouldDeleteFriend;
/// Before requesting to add a user as friend or a contact,
/// `true` means can add continually, while `false` will not add.
/// You can make a second confirmation here by a modal, etc.
FutureBool Function(String userID) shouldAddFriend;
/// After getting the user info of friend or contact,
/// and before rendering it to the profile page.
FriendInfoFunction didGetFriendInfo;
/// The callback after the remark to a friend changed.
FutureBool Function(String userID) didRemarkUpdated;
ProfileLifeCycle({
this.didRemarkUpdated = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.didGetFriendInfo = DefaultLifeCycle.defaultFriendInfoSolution,
this.shouldAddToBlockList = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldAddFriend = DefaultLifeCycle.defaultAsyncBooleanSolution,
this.shouldDeleteFriend = DefaultLifeCycle.defaultAsyncBooleanSolution,
});
}
@@ -0,0 +1,169 @@
// ignore_for_file: unnecessary_getters_setters
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_change_info_type.dart';
import 'package:tencent_cloud_chat_sdk/manager/v2_tim_manager.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_change_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_topic_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_topic_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.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';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
enum UpdateType { groupInfo, memberList, joinApplicationList, groupDismissed, kickedFromGroup }
class NeedUpdate {
final String groupID;
final UpdateType updateType;
final String extraData;
int? groupInfoSubType;
String? ownerID;
NeedUpdate(this.groupID, this.updateType, this.extraData);
}
class TUIGroupListenerModel extends ChangeNotifier {
final GroupServices _groupServices = serviceLocator<GroupServices>();
V2TimGroupListener? _groupListener;
NeedUpdate? _needUpdate;
final TUIChatGlobalModel chatViewModel = serviceLocator<TUIChatGlobalModel>();
late CoreServicesImpl coreInstance = TIMUIKitCore.getInstance();
late V2TIMManager sdkInstance = TIMUIKitCore.getSDKInstance();
NeedUpdate? get needUpdate => _needUpdate;
set needUpdate(NeedUpdate? value) {
Future.delayed(const Duration(seconds: 0), () {
_needUpdate = value;
});
}
TUIGroupListenerModel() {
_groupListener = V2TimGroupListener(onMemberInvited: (groupID, opUser, memberList) {
_needUpdate = NeedUpdate(groupID, UpdateType.memberList, "");
notifyListeners();
}, 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, "");
notifyListeners();
}, onMemberLeave: (String groupID, V2TimGroupMemberInfo member) {
_needUpdate = NeedUpdate(groupID, UpdateType.memberList, "");
notifyListeners();
}, 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: (String groupID, V2TimGroupMemberInfo member, String opReason) async {
_onReceiveJoinApplication(groupID, member, opReason);
chatViewModel.refreshGroupApplicationList();
notifyListeners();
}, onGroupDismissed: (String groupID, V2TimGroupMemberInfo opUser) async {
_deleteGroupConversation(groupID);
final groupName = await _getGroupName(groupID);
_needUpdate = NeedUpdate(groupID, UpdateType.groupDismissed, groupName);
notifyListeners();
});
}
setGroupListener() {
_groupServices.addGroupListener(listener: _groupListener!);
}
removeGroupListener() {
_groupServices.removeGroupListener(listener: _groupListener!);
}
getCommunityCategoryList(String groupID) async {
final Map<String, String>? customInfo = await getCommunityCustomInfo(groupID);
if (customInfo != null) {
final String? categoryListString = customInfo["categoryList"];
if (categoryListString != null && categoryListString.isNotEmpty) {
return jsonDecode(categoryListString);
}
}
}
Future<Map<String, String>?> getCommunityCustomInfo(String groupID) async {
V2TimValueCallback<List<V2TimGroupInfoResult>> res =
await TencentImSDKPlugin.v2TIMManager.getGroupManager().getGroupsInfo(groupIDList: [groupID]);
if (res.code != 0) {
final V2TimGroupInfoResult? groupInfo = res.data?[0];
if (groupInfo != null) {
Map<String, String>? customInfo = groupInfo.groupInfo?.customInfo;
return customInfo;
}
}
return null;
}
setCommunityCategoryList(String groupID, String groupType, List<String> newCategoryList) async {
final Map<String, String>? customInfo = await getCommunityCustomInfo(groupID);
customInfo?["categoryList"] = jsonEncode(newCategoryList);
TencentImSDKPlugin.v2TIMManager.getGroupManager().setGroupInfo(
info: V2TimGroupInfo(
customInfo: customInfo,
groupID: groupID,
groupType: groupType,
// ...其他资料
));
}
addCategoryForTopic(String groupID, String categoryName) {
TencentImSDKPlugin.v2TIMManager.getGroupManager().setTopicInfo(
topicInfo: V2TimTopicInfo(customString: categoryName),
);
}
_onReceiveJoinApplication(String groupID, V2TimGroupMemberInfo member, String opReason) {
Future.delayed(const Duration(milliseconds: 500), () => chatViewModel.refreshGroupApplicationList());
}
Future<String> _getGroupName(String groupID) async {
final groupInfoList = await sdkInstance.getGroupManager().getGroupsInfo(groupIDList: [groupID]);
String groupName = TIM_t("群组");
if (groupInfoList.data != null) {
groupName = groupInfoList.data!.first!.groupInfo!.groupName!;
}
return groupName;
}
void _deleteGroupConversation(String groupID) async {
sdkInstance.getConversationManager().deleteConversation(conversationID: "group_${groupID}");
}
bool _isLoginUserKickedFromGroup(String groupID, List<V2TimGroupMemberInfo> memberList) {
final loginUserInfo = coreInstance.loginInfo;
int index = memberList.indexWhere((element) => element.userID == loginUserInfo.userID);
if (index > -1) {
return true;
}
return false;
}
}
@@ -0,0 +1,12 @@
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class UserProfile {
late V2TimFriendInfo? friendInfo;
late V2TimConversation? conversation;
UserProfile({required this.friendInfo, required this.conversation});
}
@@ -0,0 +1,174 @@
import 'dart:convert';
import 'dart:io';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/offlinePushInfo.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_cloud_custom_data.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class TUIChatModelTools {
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
OfflinePushInfo buildMessagePushInfo(V2TimMessage message, String convID, ConvType convType) {
String createJSON(String convID) {
return "{\"conversationID\": \"$convID\"}";
}
if (globalModel.chatConfig.offlinePushInfo != null) {
final customData = globalModel.chatConfig.offlinePushInfo!(message, convID, convType);
if (customData != null) {
return customData;
}
}
String title = globalModel.chatConfig.notificationTitle;
// If user provides null, use default ext.
String ext = globalModel.chatConfig.notificationExt != null
? globalModel.chatConfig.notificationExt!(message, convID, convType) ??
(convType == ConvType.c2c ? createJSON("c2c_${message.sender}") : createJSON("group_$convID"))
: (convType == ConvType.c2c ? createJSON("c2c_${message.sender}") : createJSON("group_$convID"));
String desc = message.userID ?? message.groupID ?? "";
String messageSummary = "";
switch (message.elemType) {
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
messageSummary = TIM_t("自定义消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
messageSummary = TIM_t("表情消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
messageSummary = TIM_t("文件消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
messageSummary = TIM_t("群提示消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
messageSummary = TIM_t("图片消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
messageSummary = TIM_t("位置消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
messageSummary = TIM_t("合并转发消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
messageSummary = TIM_t("语音消息");
break;
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
messageSummary = message.textElem!.text!;
break;
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
messageSummary = TIM_t("视频消息");
break;
}
if (globalModel.chatConfig.notificationBody != null) {
desc = globalModel.chatConfig.notificationBody!(message, convID, convType) ?? messageSummary;
} else {
desc = messageSummary;
}
return OfflinePushInfo(
title: title,
desc: desc,
disablePush: false,
ext: ext,
iOSSound: globalModel.chatConfig.notificationIOSSound,
androidSound: globalModel.chatConfig.notificationAndroidSound,
ignoreIOSBadge: false,
androidOPPOChannelID: globalModel.chatConfig.notificationOPPOChannelID,
androidVIVOClassification: 1,
);
}
V2TimMessage setUserInfoForMessage(V2TimMessage messageInfo, String? id) {
final loginUserInfo = _coreServices.loginUserInfo;
if (loginUserInfo != null) {
messageInfo.faceUrl = loginUserInfo.faceUrl;
messageInfo.nickName = loginUserInfo.nickName;
messageInfo.sender = loginUserInfo.userID;
}
messageInfo.timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).ceil();
messageInfo.isSelf = true;
messageInfo.id = id;
return messageInfo;
}
String getMessageSummary(V2TimMessage message, String? Function(V2TimMessage message)? abstractMessageBuilder) {
final String? customAbstractMessage = abstractMessageBuilder != null ? abstractMessageBuilder(message) : null;
if (customAbstractMessage != null) {
return customAbstractMessage;
}
final elemType = message.elemType;
switch (elemType) {
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return "[表情消息]";
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
return "[自定义消息]";
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
return "[文件消息]";
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
return "[群消息]";
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
return "[图片消息]";
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
return "[位置消息]";
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return "[合并消息]";
case MessageElemType.V2TIM_ELEM_TYPE_NONE:
return "[没有元素]";
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
return "[语音消息]";
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
return message.textElem?.text ?? "[文本消息]";
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
return "[视频消息]";
default:
return "";
}
}
String getMessageAbstract(V2TimMessage message, String? Function(V2TimMessage message)? abstractMessageBuilder) {
final messageAbstract = RepliedMessageAbstract(
summary: TIM_t(getMessageSummary(message, abstractMessageBuilder)),
elemType: message.elemType,
msgID: message.msgID,
timestamp: message.timestamp,
seq: message.seq);
return jsonEncode(messageAbstract.toJson());
}
Future<V2TimMessage?> getExistingMessageByID(
{required String msgID, required String conversationID, required ConvType conversationType}) async {
final currentHistoryMsgList = globalModel.messageListMap[conversationID] ?? [];
final int? targetIndex = currentHistoryMsgList.indexWhere((item) {
return item.msgID == msgID;
});
if (targetIndex != null && targetIndex > -1 && currentHistoryMsgList.isNotEmpty) {
return currentHistoryMsgList[targetIndex];
} else {
return null;
}
}
Future<bool> hasZeroSize(String filePath) async {
try {
final file = File(filePath);
final fileSize = await file.length();
return fileSize == 0;
} catch (e) {
return false;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,363 @@
// ignore_for_file: unnecessary_getters_setters, avoid_print
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_filter_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_role.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_role_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/group_profile_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
class TUIGroupProfileModel extends ChangeNotifier {
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
final GroupServices _groupServices = serviceLocator<GroupServices>();
final ConversationService _conversationService = serviceLocator<ConversationService>();
final MessageService _messageService = serviceLocator<MessageService>();
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
GroupProfileLifeCycle? _lifeCycle;
V2TimConversation? _conversation;
String _groupID = "";
List<V2TimFriendInfo>? _contactList;
List<V2TimGroupMemberFullInfo?>? _groupMemberList;
String _groupMemberListSeq = "0";
V2TimGroupInfo? _groupInfo;
Function(V2TimGroupMemberFullInfo groupMemberFullInfo, TapDownDetails? tapDetails)? onClickUser;
GroupProfileLifeCycle? get lifeCycle => _lifeCycle;
set lifeCycle(GroupProfileLifeCycle? value) {
_lifeCycle = value;
}
V2TimConversation? get conversation => _conversation;
set conversation(V2TimConversation? value) {
_conversation = value;
}
String get groupID => _groupID;
set groupID(String value) {
_groupID = value;
}
List<V2TimFriendInfo> get contactList => _contactList ?? [];
set contactList(List<V2TimFriendInfo> value) {
_contactList = value;
}
List<V2TimGroupMemberFullInfo?> get groupMemberList => _groupMemberList ?? [];
set groupMemberList(List<V2TimGroupMemberFullInfo?> value) {
_groupMemberList = value;
}
V2TimGroupInfo? get groupInfo => _groupInfo;
set groupInfo(V2TimGroupInfo? value) {
_groupInfo = value;
}
void loadData(String groupID) {
_groupID = groupID;
loadGroupInfo(groupID);
loadGroupMemberList(groupID: groupID);
_loadConversation();
_loadContactList();
}
loadGroupInfo(String groupID) async {
final groupInfo = await _groupServices.getGroupsInfo(groupIDList: [groupID]);
if (groupInfo != null) {
final groupRes = groupInfo.first;
if (groupRes.resultCode == 0) {
_groupInfo = groupRes.groupInfo;
}
}
notifyListeners();
}
Future<void> loadGroupMemberList({required String groupID, int count = 100, String? seq}) async {
final String? nextSeq = await _loadGroupMemberListFunction(groupID: groupID, seq: seq, count: count);
if (nextSeq != null && nextSeq != "0" && nextSeq != "") {
return await loadGroupMemberList(groupID: groupID, count: count, seq: nextSeq);
} else {
notifyListeners();
}
}
Future<String?> _loadGroupMemberListFunction({required String groupID, int count = 100, String? seq}) async {
if (seq == null || seq == "" || seq == "0") {
_groupMemberList?.clear();
}
final res = await _groupServices.getGroupMemberList(
groupID: groupID,
filter: GroupMemberFilterTypeEnum.V2TIM_GROUP_MEMBER_FILTER_ALL,
count: count,
nextSeq: seq ?? _groupMemberListSeq);
final groupMemberListRes = res.data;
if (res.code == 0 && groupMemberListRes != null) {
final groupMemberListTemp = groupMemberListRes.memberInfoList ?? [];
// TODO
outputLogger.i(
"loadGroupMemberListfinish,groupMemberListTemp, ${groupMemberListRes.nextSeq}, ${groupMemberListTemp.length}");
_groupMemberList = [...?_groupMemberList, ...groupMemberListTemp];
_groupMemberListSeq = groupMemberListRes.nextSeq ?? "0";
}
return groupMemberListRes?.nextSeq;
}
_loadConversation() async {
conversation = await _conversationService.getConversation(conversationID: "group_$_groupID");
}
_loadContactList() async {
final res = await _friendshipServices.getFriendList();
_contactList = res;
}
pinedConversation(bool isPined) async {
await _conversationService.pinConversation(conversationID: "group_$_groupID", isPinned: isPined);
conversation?.isPinned = isPined;
notifyListeners();
}
setMessageDisturb(bool value) async {
final res = await _messageService.setGroupReceiveMessageOpt(
groupID: _groupID,
opt: value ? ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE : ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE);
if (res.code == 0) {
conversation?.recvOpt =
(value ? ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE : ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE).index;
}
notifyListeners();
}
Future<V2TimValueCallback<V2GroupMemberInfoSearchResult>> searchGroupMember(
V2TimGroupMemberSearchParam searchParam) async {
final res = await _groupServices.searchGroupMembers(searchParam: searchParam);
if (res.code == 0) {}
return res;
}
Future<V2TimCallback?> setGroupName(String groupName) async {
if (_groupInfo != null) {
String? originalGroupName = _groupInfo?.groupName;
_groupInfo?.groupName = groupName;
V2TimGroupInfo v2timGroupInfo = V2TimGroupInfo(groupID: _groupID, groupType: _groupInfo!.groupType);
v2timGroupInfo.groupName = groupName;
final response = await _groupServices.setGroupInfo(info: v2timGroupInfo);
if (response.code != 0) {
_groupInfo?.groupName = originalGroupName;
}
notifyListeners();
return response;
}
return null;
}
setGroupNotification(String notification) async {
if (_groupInfo != null) {
V2TimGroupInfo v2timGroupInfo = V2TimGroupInfo(groupID: _groupID, groupType: _groupInfo!.groupType);
v2timGroupInfo.notification = notification;
final response = await _groupServices.setGroupInfo(info: v2timGroupInfo);
if (response.code == 0) {
notifyListeners();
_groupInfo?.notification = notification;
}
}
}
String getSelfNameCard() {
try {
final loginUserID = _coreServices.loginUserInfo?.userID;
String nameCard = "";
if (_groupMemberList != null) {
nameCard = groupMemberList.firstWhere((element) => element?.userID == loginUserID)?.nameCard ?? "";
}
return nameCard;
} catch (err) {
return "";
}
}
Future<V2TimCallback?> setNameCard(String nameCard) async {
final loginUserID = _coreServices.loginUserInfo?.userID;
if (loginUserID != null) {
final res = await _groupServices.setGroupMemberInfo(groupID: _groupID, userID: loginUserID, nameCard: nameCard);
if (res.code == 0) {
final targetIndex = _groupMemberList?.indexWhere((element) => element?.userID == loginUserID);
if (targetIndex != -1) {
_groupMemberList![targetIndex!]!.nameCard = nameCard;
notifyListeners();
}
}
return res;
}
return null;
}
Future<V2TimCallback?> setGroupAddOpt(int addOpt) async {
if (_groupInfo != null) {
int? originalAddopt = _groupInfo?.groupAddOpt;
_groupInfo?.groupAddOpt = addOpt;
V2TimGroupInfo v2timGroupInfo = V2TimGroupInfo(groupID: _groupID, groupType: _groupInfo!.groupType);
v2timGroupInfo.groupAddOpt = addOpt;
final response = await _groupServices.setGroupInfo(info: v2timGroupInfo);
if (response.code != 0) {
_groupInfo?.groupAddOpt = originalAddopt;
}
notifyListeners();
return response;
}
return null;
}
Future<V2TimCallback> setMemberToNormal(String userID) async {
final res = await _groupServices.setGroupMemberRole(
groupID: _groupID, userID: userID, role: GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_MEMBER);
if (res.code == 0) {
final targetIndex = _groupMemberList!.indexWhere((e) => e!.userID == userID);
if (targetIndex != -1) {
final targetElem = _groupMemberList![targetIndex];
targetElem?.role = GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_MEMBER;
_groupMemberList![targetIndex] = targetElem;
}
notifyListeners();
}
return res;
}
Future<V2TimCallback> setMemberToAdmin(String userID) async {
final res = await _groupServices.setGroupMemberRole(
groupID: _groupID, userID: userID, role: GroupMemberRoleTypeEnum.V2TIM_GROUP_MEMBER_ROLE_ADMIN);
if (res.code == 0) {
final targetIndex = _groupMemberList!.indexWhere((e) => e!.userID == userID);
if (targetIndex != -1) {
final targetElem = _groupMemberList![targetIndex];
targetElem?.role = GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN;
_groupMemberList![targetIndex] = targetElem;
}
notifyListeners();
}
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";
}
bool canKickOffMember() {
final isGroupOwner = _groupInfo?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER;
final isAdmin = _groupInfo?.role == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN;
if (_groupInfo?.groupType == GroupType.Work) {
/// work 群主才能踢人
return isGroupOwner;
}
if (_groupInfo?.groupType == GroupType.Public || _groupInfo?.groupType == GroupType.Meeting) {
/// public || meeting 群主和管理员可以踢人
return isGroupOwner || isAdmin;
}
return false;
}
Future<V2TimCallback?> setMuteAll(bool muteAll) async {
if (_groupInfo != null) {
_groupInfo?.isAllMuted = muteAll;
V2TimGroupInfo v2timGroupInfo = V2TimGroupInfo(groupID: _groupID, groupType: _groupInfo!.groupType);
v2timGroupInfo.isAllMuted = muteAll;
final response = await _groupServices.setGroupInfo(info: v2timGroupInfo);
if (response.code != 0) {
_groupInfo?.isAllMuted = muteAll;
}
notifyListeners();
return response;
}
return null;
}
Future<V2TimCallback?> muteGroupMember(String userID, bool isMute, int? serverTime) async {
const muteTime = 315360000;
final res = await _groupServices.muteGroupMember(groupID: _groupID, userID: userID, seconds: isMute ? muteTime : 0);
if (res.code == 0) {
final targetIndex = _groupMemberList!.indexWhere((e) => e!.userID == userID);
if (targetIndex != -1) {
final targetElem = _groupMemberList![targetIndex];
targetElem?.muteUntil = isMute ? (serverTime ?? 0) + muteTime : 0;
_groupMemberList![targetIndex] = targetElem;
}
notifyListeners();
}
return null;
}
Future<V2TimCallback> kickOffMember(List<String> userIDs) async {
final res = await _groupServices.kickGroupMember(groupID: _groupID, memberList: userIDs);
return res;
}
Future<V2TimValueCallback<List<V2TimGroupMemberOperationResult>>> inviteUserToGroup(List<String> userIDS) async {
final res = await _groupServices.inviteUserToGroup(groupID: _groupID, userList: userIDS);
return res;
}
}
@@ -0,0 +1,243 @@
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/profile_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/model/profile_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class TUIProfileViewModel extends ChangeNotifier {
final ConversationService _conversationService = serviceLocator<ConversationService>();
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
final TUIFriendShipViewModel _friendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
final CoreServicesImpl _coreServices = serviceLocator<CoreServicesImpl>();
final MessageService _messageService = serviceLocator<MessageService>();
UserProfile? _userProfile;
ProfileLifeCycle? _lifeCycle;
bool? _shouldAddToBlackList;
int _friendType = 0;
bool? _isDisturb;
UserProfile? get userProfile {
return _userProfile;
}
set userProfile(UserProfile? value) {
_userProfile = value;
notifyListeners();
}
bool? get isDisturb {
return _isDisturb;
}
bool? get isAddToBlackList {
return _shouldAddToBlackList;
}
int get friendType {
return _friendType;
}
set lifeCycle(ProfileLifeCycle? value) {
_lifeCycle = value;
}
loadData({required String userID, bool isNeedConversation = true}) async {
if (userID.isEmpty) {
return;
}
V2TimFriendInfo? friendUserInfo;
V2TimConversation? conversation;
final userInfoList = await _friendshipServices.getFriendsInfo(userIDList: [userID]);
final checkFriend =
await _friendshipServices.checkFriend(userIDList: [userID], checkType: FriendTypeEnum.V2TIM_FRIEND_TYPE_SINGLE);
if (checkFriend != null) {
final res = checkFriend.first;
if (res.resultCode == 0) {
_friendType = res.resultType;
}
}
if (userInfoList != null) {
friendUserInfo = userInfoList[0].friendInfo;
}
if (isNeedConversation) {
conversation = await _conversationService.getConversation(conversationID: "c2c_$userID");
_isDisturb = conversation?.recvOpt == 2;
}
final friendInfo = await _lifeCycle?.didGetFriendInfo(friendUserInfo) ?? friendUserInfo;
_isDisturb = conversation?.recvOpt == 2;
_userProfile = UserProfile(friendInfo: friendInfo, conversation: conversation);
_shouldAddToBlackList = _friendShipViewModel.blockList.indexWhere((element) => element.userID == userID) > -1;
notifyListeners();
}
Future<V2TimCallback> pinedConversation(bool isPined, String convID) async {
final res = await _conversationService.pinConversation(conversationID: convID, isPinned: isPined);
_userProfile?.conversation!.isPinned = isPined;
notifyListeners();
return res;
}
Future<List<V2TimFriendOperationResult>?> addToBlackList(bool shouldAdd, String userID) async {
if (_lifeCycle?.shouldAddToBlockList != null && await _lifeCycle!.shouldAddToBlockList(userID) == false) {
return null;
}
if (shouldAdd) {
final res = await _friendshipServices.addToBlackList(userIDList: [userID]);
if (res != null && res.isNotEmpty) {
final result = res.first;
if (result.resultCode == 0) {
_shouldAddToBlackList = true;
_friendType = 0;
}
}
notifyListeners();
return res;
} else {
final res = await _friendshipServices.deleteFromBlackList(userIDList: [userID]);
if (res != null && res.isNotEmpty) {
final result = res.first;
if (result.resultCode == 0) {
_shouldAddToBlackList = false;
final checkFriend = await _friendshipServices
.checkFriend(userIDList: [userID], checkType: FriendTypeEnum.V2TIM_FRIEND_TYPE_SINGLE);
if (checkFriend != null) {
final res = checkFriend.first;
_friendType = res.resultType;
}
}
}
_friendShipViewModel.loadBlockListData();
notifyListeners();
return res;
}
}
Future<V2TimFriendOperationResult?> deleteFriend(String userID, {bool needUpdateData = true}) async {
if (_lifeCycle?.shouldDeleteFriend != null && await _lifeCycle!.shouldDeleteFriend(userID) == false) {
return null;
}
final res = await _friendshipServices
.deleteFromFriendList(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;
}
Future<V2TimCallback> changeFriendVerificationMethod(int allowType) async {
V2TimUserFullInfo userFullInfo = V2TimUserFullInfo();
userFullInfo.allowType = allowType;
final res = await _coreServices.setSelfInfo(userFullInfo: userFullInfo);
if (res.code == 0) {
_userProfile?.friendInfo!.userProfile!.allowType = allowType;
notifyListeners();
}
return res;
}
Future<V2TimFriendOperationResult?> addFriend(String userID) async {
if (_lifeCycle?.shouldAddFriend != null && await _lifeCycle!.shouldAddFriend(userID) == false) {
return null;
}
final res = await _friendshipServices.addFriend(userID: userID, addType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH);
if (res.code == 0) {
loadData(userID: userID);
return res.data;
}
return null;
}
Future<V2TimCallback> updateRemarks(String userID, String remark) async {
final res = await _friendshipServices.setFriendInfo(userID: userID, friendRemark: remark);
if (res.code == 0) {
_userProfile?.friendInfo!.friendRemark = remark;
notifyListeners();
}
return res;
}
Future<V2TimCallback> setMessageDisturb(String userID, bool isDisturb) async {
final res = await _messageService.setC2CReceiveMessageOpt(
userIDList: [userID],
opt: isDisturb ? ReceiveMsgOptEnum.V2TIM_RECEIVE_NOT_NOTIFY_MESSAGE : ReceiveMsgOptEnum.V2TIM_RECEIVE_MESSAGE);
if (res.code == 0) {
_isDisturb = isDisturb;
}
notifyListeners();
return res;
}
updateUserInfo(V2TimUserFullInfo userFullInfo) {
if (userFullInfo.nickName != null) {
_userProfile?.friendInfo!.userProfile?.nickName = userFullInfo.nickName;
}
if (userFullInfo.faceUrl != null) {
_userProfile?.friendInfo!.userProfile?.faceUrl = userFullInfo.faceUrl;
}
if (userFullInfo.selfSignature != null) {
_userProfile?.friendInfo!.userProfile?.selfSignature = userFullInfo.selfSignature;
}
if (userFullInfo.gender != null) {
_userProfile?.friendInfo!.userProfile?.gender = userFullInfo.gender;
}
if (userFullInfo.allowType != null) {
_userProfile?.friendInfo!.userProfile?.allowType = userFullInfo.allowType;
}
if (userFullInfo.customInfo != null) {
_userProfile?.friendInfo!.userProfile?.customInfo = userFullInfo.customInfo;
}
if (userFullInfo.role != null) {
_userProfile?.friendInfo!.userProfile?.role = userFullInfo.role;
}
if (userFullInfo.level != null) {
_userProfile?.friendInfo!.userProfile?.level = userFullInfo.level;
}
if (userFullInfo.birthday != null) {
_userProfile?.friendInfo!.userProfile?.birthday = userFullInfo.birthday;
}
}
Future<V2TimCallback> updateSelfInfo(V2TimUserFullInfo userFullInfo) async {
final res = await _coreServices.setSelfInfo(userFullInfo: userFullInfo);
if (res.code == 0) {
updateUserInfo(userFullInfo);
notifyListeners();
}
return res;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,335 @@
// ignore_for_file: unnecessary_getters_setters
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_search_param.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/conversation_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
List<T> removeDuplicates<T>(List<T> list, bool Function(T first, T second) isEqual) {
List<T> output = [];
for (var i = 0; i < list.length; i++) {
bool found = false;
for (var j = 0; j < output.length; j++) {
if (isEqual(list[i], output[j])) {
found = true;
}
}
if (!found) {
output.add(list[i]);
}
}
return output;
}
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>();
final TUIChatGlobalModel _chatGlobalModel = serviceLocator<TUIChatGlobalModel>();
final MessageService _messageService = serviceLocator<MessageService>();
late V2TimConversationListener _conversationListener;
List<V2TimConversation?> _conversationList = [];
static V2TimConversation? _selectedConversation;
Map<String, String> webDraftMap = {};
bool _haveMoreData = true;
int _totalUnReadCount = 0;
String? _scrollToConversation;
final TUIChatGlobalModel globalChatModel = serviceLocator<TUIChatGlobalModel>();
String _nextSeq = "0";
ConversationLifeCycle? _lifeCycle;
List<V2TimConversation?> get conversationList {
if (PlatformUtils().isWeb) {
try {
_conversationList.sort((a, b) {
return b!.lastMessage!.timestamp!.compareTo(a!.lastMessage!.timestamp!);
});
final pinnedConversation = _conversationList.where((element) => element?.isPinned == true).toList();
_conversationList.removeWhere((element) => element?.isPinned == true);
_conversationList = [...pinnedConversation, ..._conversationList];
// ignore: empty_catches
} catch (e) {}
} else {
_conversationList.sort((a, b) => b!.orderkey!.compareTo(a!.orderkey!));
}
return _conversationList;
}
V2TimConversation? getConversation(String conversationID) {
return _conversationList.firstWhereOrNull((element) => element?.conversationID == conversationID);
}
String? get scrollToConversation => _scrollToConversation;
set scrollToConversation(String? value) {
_scrollToConversation = value;
notifyListeners();
}
void clearScrollToConversation() {
_scrollToConversation = null;
}
bool get haveMoreData {
return _haveMoreData;
}
int get totalUnReadCount => _totalUnReadCount;
set totalUnReadCount(int value) {
_totalUnReadCount = value;
}
set lifeCycle(ConversationLifeCycle? value) {
_lifeCycle = value;
}
set conversationList(List<V2TimConversation?> conversationList) {
_conversationList = conversationList;
notifyListeners();
}
set selectedConversation(V2TimConversation? value) {
_selectedConversation = value;
notifyListeners();
}
V2TimConversation? get selectedConversation {
return _selectedConversation;
}
static V2TimConversation? of() {
return _selectedConversation;
}
TUIConversationViewModel() {
_conversationListener = V2TimConversationListener(onConversationChanged: (conversationList) {
_onConversationListChanged(conversationList);
}, onNewConversation: (conversationList) {
_addNewConversation(conversationList);
}, onTotalUnreadMessageCountChanged: (totalUnread) {
_totalUnReadCount = totalUnread;
_chatGlobalModel.totalUnReadCount = totalUnread;
notifyListeners();
}, onSyncServerFinish: () {
// Remove the process to load such a many of conversations after launching
if (!PlatformUtils().isWeb) {
loadInitConversation();
}
}, 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);
}
}
});
}
loadInitConversation() async {
await loadData(count: 40);
// Remove the process to load such a many of conversations after launching
// if (selfInfoViewModel.globalConfig?.isPreloadMessagesAfterInit ?? true) {
// _chatGlobalModel.initMessageMapFromLocalDatabase(_conversationList);
// }
}
initConversation() async {
clearData();
loadInitConversation();
}
Future<void> loadData({required int count}) async {
_haveMoreData = true;
final isRefresh = _nextSeq == "0";
final conversationResult = await _conversationService.getConversationList(nextSeq: _nextSeq, count: count);
_nextSeq = conversationResult?.nextSeq ?? "";
final conversationList = conversationResult?.conversationList;
if (conversationList != null) {
if (conversationList.isEmpty || conversationList.length < count) {
_haveMoreData = false;
}
List<V2TimConversation?> combinedConversationList = [];
if (isRefresh) {
combinedConversationList = conversationList;
} else {
combinedConversationList = [..._conversationList, ...conversationList];
}
final List<V2TimConversation?> finalConversationList =
await _lifeCycle?.conversationListWillMount(combinedConversationList) ?? combinedConversationList;
_conversationList = removeDuplicates<V2TimConversation?>(
finalConversationList, (item1, item2) => item1?.conversationID == item2?.conversationID);
notifyListeners();
}
_totalUnReadCount = await _conversationService.getTotalUnreadCount();
notifyListeners();
return;
}
void setSelectedConversation(V2TimConversation conversation) {
_selectedConversation = conversation;
notifyListeners();
}
Future<V2TimCallback> pinConversation({
required String conversationID,
required bool isPinned,
}) {
return _conversationService.pinConversation(conversationID: conversationID, isPinned: isPinned);
}
Future<V2TimCallback?> clearHistoryMessage({required String convID, required int convType}) async {
if (_lifeCycle?.shouldClearHistoricalMessageForConversation != null &&
await _lifeCycle!.shouldClearHistoricalMessageForConversation(convID) == false) {
return null;
}
globalChatModel.setMessageList(convID, []);
if (convType == 1) {
return _messageService.clearC2CHistoryMessage(userID: convID);
} else {
return _messageService.clearGroupHistoryMessage(groupID: convID);
}
}
searchFriends(String searchKey) async {
final res = await _friendshipServices.searchFriends(searchParam: V2TimFriendSearchParam(keywordList: [searchKey]));
return res;
}
Future<V2TimCallback?> deleteConversation({required String conversationID}) async {
if (_lifeCycle?.shouldDeleteConversation != null &&
await _lifeCycle!.shouldDeleteConversation(conversationID) == false) {
return null;
}
final res = await _conversationService.deleteConversation(conversationID: conversationID);
if (res.code == 0) {
_conversationList.removeWhere((element) => element?.conversationID == conversationID);
notifyListeners();
}
return res;
}
_onConversationListChanged(List<V2TimConversation> list) {
for (int element = 0; element < list.length; element++) {
int index = _conversationList.indexWhere((item) => item!.conversationID == list[element].conversationID);
if (index > -1) {
_conversationList.setAll(index, [list[element]] as List<V2TimConversation?>);
} else {
_conversationList.add(list[element]);
}
}
notifyListeners();
}
_onConversationDeleted(List<String> list) {
for (int i = 0; i < list.length; i++) {
int index = _conversationList.indexWhere((item) => item!.conversationID == list[i]);
if (index > -1) {
_conversationList.removeAt(index);
_conversationList = removeDuplicates<V2TimConversation?>(
_conversationList, (item1, item2) => item1?.conversationID == item2?.conversationID);
}
}
notifyListeners();
}
_addNewConversation(List<V2TimConversation> list) {
_conversationList.addAll(list);
_conversationList = removeDuplicates<V2TimConversation?>(
_conversationList, (item1, item2) => item1?.conversationID == item2?.conversationID);
notifyListeners();
}
setConversationListener() {
_conversationService.addConversationListener(listener: _conversationListener);
}
removeConversationListener() {
_conversationService.removeConversationListener(listener: _conversationListener);
}
Future<V2TimCallback> setConversationDraft({
required String conversationID,
String? draftText,
bool isTopic = false,
String? groupID,
bool isAllowWeb = true,
}) async {
assert(!isTopic || (groupID != null && groupID.isNotEmpty),
"When 'isTopic' is true, 'groupID' must not be null or empty.");
if (PlatformUtils().isWeb && isAllowWeb) {
webDraftMap[conversationID] = draftText ?? "";
return V2TimCallback(code: 0, desc: "");
} else {
if (isTopic) {
final topicInfoList = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.getTopicInfoList(groupID: groupID!, topicIDList: [conversationID]);
final topicInfo = topicInfoList.data?.first.topicInfo;
topicInfo?.draftText = draftText;
final res = await TencentImSDKPlugin.v2TIMManager.getGroupManager().setTopicInfo(topicInfo: topicInfo!);
return res;
} else {
return _conversationService.setConversationDraft(conversationID: conversationID, draftText: draftText);
}
}
}
clearWebDraft({
required String conversationID,
}) {
webDraftMap[conversationID] = "";
}
String? getWebDraft({
required String conversationID,
}) {
return TencentUtils.checkString(webDraftMap[conversationID]);
}
clearData() {
_conversationList = [];
_selectedConversation = null;
_nextSeq = "0";
_haveMoreData = true;
notifyListeners();
}
refresh({int count = 100}) {
_nextSeq = "0";
_haveMoreData = true;
loadData(count: count);
}
}
@@ -0,0 +1,266 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_response_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_application.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_application.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_status.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/block_list_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/friend_list_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/new_contact_life_cycle.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';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class TUIFriendShipViewModel extends ChangeNotifier {
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
final GroupServices _groupServices = serviceLocator<GroupServices>();
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
late V2TimFriendshipListener friendShipListener;
List<V2TimFriendApplication?>? _friendApplicationList;
List<V2TimFriendInfo>? _friendList;
List<V2TimGroupInfo>? _groupList;
List<V2TimUserStatus>? _userStatusList;
int _friendApplicationAmount = 0;
List<V2TimFriendInfo>? _blockList;
NewContactLifeCycle? _newContactLifeCycle;
FriendListLifeCycle? _contactListLifeCycle;
BlockListLifeCycle? _blockListLifeCycle;
set newContactLifeCycle(NewContactLifeCycle? value) {
_newContactLifeCycle = value;
}
set contactListLifeCycle(FriendListLifeCycle? value) {
_contactListLifeCycle = value;
}
set blockListLifeCycle(BlockListLifeCycle? value) {
_blockListLifeCycle = value;
}
set userStatusList(List<V2TimUserStatus> value) {
_userStatusList = value;
notifyListeners();
}
List<V2TimUserStatus> get userStatusList => _userStatusList ?? [];
List<V2TimFriendInfo> get blockList {
return _blockList ?? [];
}
List<V2TimGroupInfo> get groupList {
return _groupList ?? [];
}
List<V2TimFriendInfo>? get friendList {
return _friendList;
}
int get friendApplicationAmount => _friendApplicationAmount;
List<V2TimFriendApplication?>? get friendApplicationList => _friendApplicationList;
TUIFriendShipViewModel() {
friendShipListener = V2TimFriendshipListener(
onFriendApplicationListAdded: (applicationList) {
loadContactApplicationData();
},
onFriendApplicationListDeleted: (userIDList) {
loadContactApplicationData();
},
onFriendApplicationListRead: () {
loadContactApplicationData();
},
onFriendInfoChanged: (infoList) {
loadContactListData();
},
onFriendListAdded: (users) async {
await loadContactListData();
loadUserStatus();
},
onFriendListDeleted: (userList) async {
await loadContactListData();
loadUserStatus();
},
onBlackListAdd: (infoList) async {
await loadBlockListData();
loadUserStatus();
},
onBlackListDeleted: (userList) async {
await loadBlockListData();
loadUserStatus();
},
);
}
initFriendShipModel() {
loadData();
}
loadData() async {
loadContactApplicationData();
loadBlockListData();
await loadContactListData();
loadUserStatus();
}
clearData() {
_friendApplicationList = [];
_friendApplicationAmount = 0;
_friendList = [];
_userStatusList = [];
_blockList = [];
notifyListeners();
}
loadUserStatus() async {
if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false || friendList == null || friendList!.isEmpty) {
return;
}
final List<List<String>> userIDSet = [];
final int needHowManyRequest = ((friendList!.length) / 500).ceil();
final int amountEachRequest = ((friendList!.length) / needHowManyRequest).ceil();
for (int i = 0; i < needHowManyRequest; i++) {
userIDSet.add(friendList!
.getRange(i * amountEachRequest, min(friendList!.length, (i + 1) * amountEachRequest))
.map((e) => e.userID)
.toList());
}
final List<List<V2TimUserStatus>> userStatus = await Future.wait([
...userIDSet.map((userIDList) async {
return await _friendshipServices.getUserStatus(userIDList: userIDList);
})
]);
final List<V2TimUserStatus> flatUserStatus = [];
for (var e in userStatus) {
flatUserStatus.addAll(e);
}
userStatusList = flatUserStatus;
}
loadContactApplicationData() async {
final newContactRes = await _friendshipServices.getFriendApplicationList();
// Only Received Application
_friendApplicationList = newContactRes?.friendApplicationList
?.where((item) => item!.type == FriendApplicationTypeEnum.V2TIM_FRIEND_APPLICATION_COME_IN.index)
.toList();
_friendApplicationAmount = _friendApplicationList?.length ?? 0;
notifyListeners();
}
Future<void> loadContactListData() async {
final List<V2TimFriendInfo> res = await _friendshipServices.getFriendList() ?? [];
final memberList = await _contactListLifeCycle?.friendListWillMount(res) ?? res;
_friendList = memberList;
notifyListeners();
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 ?? [];
notifyListeners();
return;
}
loadGroupListData() async {
final groupListRes = await _groupServices.getJoinedGroupList();
_groupList = groupListRes ?? [];
if (_groupList != null && _groupList!.isNotEmpty) {
notifyListeners();
}
return;
}
Future<List<V2TimFriendOperationResult>?> deleteFromBlockList(List<String> userIDList) async {
if (_blockListLifeCycle?.shouldDeleteFromBlockList != null &&
await _blockListLifeCycle!.shouldDeleteFromBlockList(userIDList) == false) {
return null;
}
final res = await _friendshipServices.deleteFromBlackList(userIDList: userIDList);
if (res != null) {
return res;
}
return null;
}
Future<V2TimFriendOperationResult?> acceptFriendApplication(
String userID,
int type,
) async {
if (_newContactLifeCycle?.shouldAcceptContactApplication != null &&
await _newContactLifeCycle!.shouldAcceptContactApplication(userID) == false) {
return null;
}
final res = await _friendshipServices.acceptFriendApplication(
responseType: FriendResponseTypeEnum.V2TIM_FRIEND_ACCEPT_AGREE_AND_ADD,
type: FriendApplicationTypeEnum.values[type],
userID: userID,
);
if (res != null) {
return res;
}
return null;
}
Future<V2TimFriendOperationResult?> refuseFriendApplication(
String userID,
int type,
) async {
if (_newContactLifeCycle?.shouldRefuseContactApplication != null &&
await _newContactLifeCycle!.shouldRefuseContactApplication(userID) == false) {
return null;
}
final res = await _friendshipServices.refuseFriendApplication(
type: FriendApplicationTypeEnum.values[type],
userID: userID,
);
if (res != null) {
return res;
}
return null;
}
Future<List<V2TimGroupMemberFullInfo?>> getGroupMembersInfo(
{required String groupID, required List<String> memberList}) async {
final res = await _groupServices.getGroupMembersInfo(groupID: groupID, memberList: memberList);
return res.data ?? [];
}
addFriendListener({V2TimFriendshipListener? listener}) {
_friendshipServices.addFriendListener(listener: friendShipListener);
}
removeFriendshipListener({V2TimFriendshipListener? listener}) {
_friendshipServices.removeFriendListener(listener: friendShipListener);
}
}
@@ -0,0 +1,149 @@
// ignore_for_file: constant_identifier_names
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_result_item.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_result_item.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
enum KeywordListMatchType { V2TIM_KEYWORD_LIST_MATCH_TYPE_OR, V2TIM_KEYWORD_LIST_MATCH_TYPE_AND }
class TUISearchViewModel extends ChangeNotifier {
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
final MessageService _messageService = serviceLocator<MessageService>();
final ConversationService _conversationService = serviceLocator<ConversationService>();
final GroupServices _groupServices = serviceLocator<GroupServices>();
List<V2TimFriendInfoResult>? friendList = [];
List<V2TimMessageSearchResultItem>? msgList = [];
int msgPage = 0;
int totalMsgCount = 0;
int totalMsgInConversationCount = 0;
List<V2TimMessage> currentMsgListForConversation = [];
List<V2TimGroupInfo>? groupList = [];
List<V2TimConversation?> conversationList = [];
Future<List<V2TimConversation?>?> initConversationMsg() async {
final conversationResult = await _conversationService.getConversationList(nextSeq: "0", count: 500);
final conversationListData = conversationResult?.conversationList;
conversationList = conversationListData ?? [];
notifyListeners();
return conversationListData;
}
void initSearch() {
friendList = [];
msgList = [];
groupList = [];
totalMsgCount = 0;
notifyListeners();
}
void searchFriendByKey(String searchKey) async {
final searchResult =
await _friendshipServices.searchFriends(searchParam: V2TimFriendSearchParam(keywordList: [searchKey]));
friendList = searchResult;
notifyListeners();
}
void searchGroupByKey(String searchKey) async {
final searchResult =
await _groupServices.searchGroups(searchParam: V2TimGroupSearchParam(keywordList: [searchKey]));
groupList = searchResult.data ?? [];
notifyListeners();
}
void getMsgForConversation(String keyword, String conversationId, int page) async {
void clearData() {
currentMsgListForConversation = [];
totalMsgInConversationCount = 0;
}
if (page == 0) {
clearData();
}
if (keyword.isEmpty) {
clearData();
return;
}
final searchResult = await _messageService.searchLocalMessages(
searchParam: V2TimMessageSearchParam(
keywordList: [keyword],
pageIndex: page,
pageSize: 30,
searchTimePeriod: 0,
searchTimePosition: 0,
conversationID: conversationId,
type: KeywordListMatchType.V2TIM_KEYWORD_LIST_MATCH_TYPE_OR.index,
));
if (searchResult.code == 0 && searchResult.data != null) {
final messageSearchResultItems = searchResult.data!.messageSearchResultItems!
.firstWhereOrNull((element) => element.conversationID == conversationId);
totalMsgInConversationCount = messageSearchResultItems?.messageCount ?? 0;
currentMsgListForConversation = [
...currentMsgListForConversation,
...(messageSearchResultItems?.messageList ?? [])
];
}
notifyListeners();
}
void searchMsgByKey(String searchKey, bool isFirst) async {
if (isFirst == true) {
msgPage = 0;
msgList = [];
totalMsgCount = 0;
}
final searchResult = await _messageService.searchLocalMessages(
searchParam: V2TimMessageSearchParam(
keywordList: [searchKey],
pageIndex: msgPage,
pageSize: 5,
searchTimePeriod: 0,
searchTimePosition: 0,
type: KeywordListMatchType.V2TIM_KEYWORD_LIST_MATCH_TYPE_OR.index,
));
if (searchResult.code == 0 && searchResult.data != null) {
msgPage++;
msgList = [...?msgList, ...?searchResult.data!.messageSearchResultItems];
totalMsgCount = searchResult.data!.totalCount ?? 0;
}
notifyListeners();
}
void searchByKey(String? searchKey) async {
if (searchKey == null || searchKey.isEmpty) {
friendList = [];
groupList = [];
msgList = [];
totalMsgCount = 0;
notifyListeners();
} else {
searchFriendByKey(searchKey);
searchMsgByKey(searchKey, true);
searchGroupByKey(searchKey);
}
}
}
@@ -0,0 +1,25 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_config.dart';
class TUISelfInfoViewModel extends ChangeNotifier {
V2TimUserFullInfo? _loginInfo;
TIMUIKitConfig? _globalConfig;
TIMUIKitConfig? get globalConfig => _globalConfig;
set globalConfig(TIMUIKitConfig? value) {
_globalConfig = value;
notifyListeners();
}
V2TimUserFullInfo? get loginInfo {
return _loginInfo;
}
setLoginInfo(V2TimUserFullInfo? value) {
_loginInfo = value;
notifyListeners();
}
}
@@ -0,0 +1,26 @@
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
class TUISettingModel extends ChangeNotifier {
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
final String keyTitle = "tencent_chat_uikit_";
double? _keyboardHeight;
double get keyboardHeight => _keyboardHeight ?? 0;
set keyboardHeight(double value) {
if(value > 40 && _keyboardHeight != value){
_keyboardHeight = value;
updateLocalSetting("${keyTitle}keyboardHeight", value.toString());
}
}
updateLocalSetting(String key, String value) async {
SharedPreferences prefs = await _prefs;
prefs.setString(key, value);
}
init() async {
SharedPreferences prefs = await _prefs;
_keyboardHeight = double.parse(prefs.getString("${keyTitle}keyboardHeight") ?? "0");
}
}
BIN
View File
Binary file not shown.
@@ -0,0 +1,39 @@
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation_result.dart';
abstract class ConversationService {
Future<V2TimConversationResult?> getConversationList({
required String nextSeq,
required int count,
});
Future<V2TimConversation?> getConversation({
required String conversationID,
});
Future<V2TimCallback> pinConversation({
required String conversationID,
required bool isPinned,
});
Future<V2TimCallback> deleteConversation({
required String conversationID,
});
Future<void> addConversationListener({
required V2TimConversationListener listener,
});
Future<V2TimCallback> setConversationDraft({required String conversationID, String? draftText});
Future<void> removeConversationListener({V2TimConversationListener? listener});
Future<int> getTotalUnreadCount();
Future<V2TimConversation?> getConversationListByConversationId({required String convID});
}
@@ -0,0 +1,123 @@
import 'package:tencent_cloud_chat_sdk/enum/V2TimConversationListener.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation_result.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
class ConversationServicesImpl extends ConversationService {
final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>();
@override
Future<V2TimConversationResult?> getConversationList({
required String nextSeq,
required int count,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.getConversationList(nextSeq: nextSeq, count: count);
if (result.code == 0) {
return result.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
return null;
}
}
@override
Future<V2TimCallback> pinConversation({
required String conversationID,
required bool isPinned,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.pinConversation(conversationID: conversationID, isPinned: isPinned);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> deleteConversation({
required String conversationID,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.deleteConversation(conversationID: conversationID);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<void> addConversationListener({
required V2TimConversationListener listener,
}) {
return TencentImSDKPlugin.v2TIMManager.getConversationManager().addConversationListener(listener: listener);
}
@override
Future<V2TimConversation?> getConversation({
required String conversationID,
}) async {
final res =
await TencentImSDKPlugin.v2TIMManager.getConversationManager().getConversation(conversationID: conversationID);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
return null;
}
@override
Future<V2TimCallback> setConversationDraft({required String conversationID, String? draftText}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.setConversationDraft(conversationID: conversationID, draftText: draftText);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<void> removeConversationListener({V2TimConversationListener? listener}) {
return TencentImSDKPlugin.v2TIMManager.getConversationManager().removeConversationListener(listener: listener);
}
@override
Future<V2TimConversation?> getConversationListByConversationId({required String convID}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getConversationManager()
.getConversationListByConversationIds(conversationIDList: [convID]);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return (result.data != null && result.data!.isNotEmpty) ? result.data![0] : null;
}
@override
Future<int> getTotalUnreadCount() async {
final res = await TencentImSDKPlugin.v2TIMManager.getConversationManager().getTotalUnreadMessageCount();
if (res.code == 0) {
return res.data ?? 0;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return 0;
}
}
+106
View File
@@ -0,0 +1,106 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_config.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
enum AppStatus { foreground, background }
enum LanguageEnum {
/// Chinese, Traditional
zhHant,
/// Chinese, Simplified
zhHans,
/// English
en,
/// Korean
ko,
/// Japanese
ja,
}
const languageEnumToString = {
LanguageEnum.zhHant: "zh-Hant",
LanguageEnum.zhHans: "zh-Hans",
LanguageEnum.en: "en",
LanguageEnum.ja: "ja",
LanguageEnum.ko: "ko",
};
abstract class CoreServices {
Future<bool?> init({
required int sdkAppID,
required LogLevelEnum loglevel,
required V2TimSDKListener listener,
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
TIMUIKitConfig? config,
/// only support "en" and "zh" temporally
LanguageEnum? language,
});
Future<void> setDataFromNative({
required String userId,
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
TIMUIKitConfig? config,
/// only support "en" and "zh" temporally
LanguageEnum? language,
});
Future login({
required String userID,
required String userSig,
});
Future logout();
Future logoutWithoutClearData();
Future unInit();
Future<V2TimValueCallback<List<V2TimUserFullInfo>>> getUsersInfo({
required List<String> userIDList,
});
// 注意:uikit的离线推送不支持TPNS
// Note: uikit's offline push do not supports TPNS
Future<V2TimCallback> setOfflinePushConfig({
bool isTPNSToken = false,
int businessID,
required String token,
});
Future<V2TimCallback> setSelfInfo({
required V2TimUserFullInfo userFullInfo,
});
Future<V2TimCallback> setOfflinePushStatus({
required AppStatus status,
int? totalCount,
});
setTheme({required TUITheme theme});
setDarkTheme();
setLightTheme();
setDeviceType(DeviceType deviceType);
}
@@ -0,0 +1,420 @@
// ignore_for_file: avoid_print
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:tencent_chat_i18n_tool/tools/i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_status.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_setting_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_config.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support.dart'
if (dart.library.html) 'package:tencent_cloud_chat_uikit/data_services/core/web_support/uikit_web_support_implement.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme_view_model.dart';
typedef EmptyAvatarBuilder = Widget Function(BuildContext context);
class LoginInfo {
final String userID;
final String userSig;
final int sdkAppID;
final V2TimUserFullInfo? loginUser;
LoginInfo({this.sdkAppID = 0, this.userSig = "", this.userID = "", this.loginUser});
}
class CoreServicesImpl implements CoreServices {
V2TimUserFullInfo? _loginInfo;
late int _sdkAppID;
late String _userID;
late String _userSig;
ValueChanged<TIMCallback>? onCallback;
VoidCallback? webLoginSuccess;
bool isLoginSuccess = false;
V2TimUserFullInfo? get loginUserInfo {
return _loginInfo;
}
LoginInfo get loginInfo {
return LoginInfo(sdkAppID: _sdkAppID, userID: _userID, userSig: _userSig, loginUser: _loginInfo);
}
EmptyAvatarBuilder? _emptyAvatarBuilder;
EmptyAvatarBuilder? get emptyAvatarBuilder {
return _emptyAvatarBuilder;
}
setEmptyAvatarBuilder(EmptyAvatarBuilder builder) {
_emptyAvatarBuilder = builder;
}
setGlobalConfig(TIMUIKitConfig? config) {
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
final TUISettingModel settingModel = serviceLocator<TUISettingModel>();
selfInfoViewModel.globalConfig = config;
settingModel.init();
}
addIdentifier() {
TUIKitWebSupport.addSetterToWindow();
TUIKitWebSupport.addIdentifierToWindow();
}
@override
Future<bool?> init(
{
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
required int sdkAppID,
required LogLevelEnum loglevel,
required V2TimSDKListener listener,
LanguageEnum? language,
String? extraLanguage,
TIMUIKitConfig? config,
/// Specify the current device platform, mobile or desktop, based on your needs.
/// TUIKit will automatically determine the platform if no specification is provided. DeviceType? platform,
DeviceType? platform,
String? uikitLogPath,
VoidCallback? onWebLoginSuccess}) async {
if (platform != null) {
TUIKitScreenUtils.deviceType = platform;
}
addIdentifier();
if (extraLanguage != null) {
Future.delayed(const Duration(milliseconds: 1), () {
I18nUtils(null, extraLanguage);
});
} else if (language != null) {
Future.delayed(const Duration(milliseconds: 1), () {
I18nUtils(null, languageEnumToString[language]);
});
}
if (onTUIKitCallbackListener != null) {
onCallback = onTUIKitCallbackListener;
}
setGlobalConfig(config);
_sdkAppID = sdkAppID;
webLoginSuccess = onWebLoginSuccess;
final result = await TencentImSDKPlugin.v2TIMManager.initSDK(
sdkAppID: sdkAppID,
loglevel: loglevel,
listener: V2TimSDKListener(
onConnectFailed: listener.onConnectFailed,
onConnectSuccess: () {
if (PlatformUtils().isWeb) {
didLoginSuccess();
if (onWebLoginSuccess != null) {
onWebLoginSuccess();
}
}
listener.onConnectSuccess();
},
onConnecting: listener.onConnecting,
onKickedOffline: listener.onKickedOffline,
onUserStatusChanged: (List<V2TimUserStatus> userStatusList) {
updateUserStatusList(userStatusList);
listener.onUserStatusChanged(userStatusList);
},
onSelfInfoUpdated: (V2TimUserFullInfo info) {
listener.onSelfInfoUpdated(info);
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(info);
_loginInfo = info;
},
onUserSigExpired: listener.onUserSigExpired));
if (result.code == 0) {
return true;
} else {
return false;
}
}
/// This method is used for init the TUIKit after you initialized the IM SDK from Native SDK.
@override
Future<void> setDataFromNative({
/// Callback from TUIKit invoke, includes IM SDK API error, notify information, Flutter error.
ValueChanged<TIMCallback>? onTUIKitCallbackListener,
LanguageEnum? language,
TIMUIKitConfig? config,
String? extraLanguage,
required String userId,
}) async {
_userID = userId;
if (extraLanguage != null) {
Future.delayed(const Duration(milliseconds: 1), () {
I18nUtils(null, extraLanguage);
});
} else if (language != null) {
Future.delayed(const Duration(milliseconds: 1), () {
I18nUtils(null, languageEnumToString[language]);
});
}
if (onTUIKitCallbackListener != null) {
onCallback = onTUIKitCallbackListener;
}
setGlobalConfig(config);
if (!PlatformUtils().isWeb) {
didLoginSuccess();
}
}
void addInitListener() {
final TUIFriendShipViewModel tuiFriendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel = serviceLocator<TUIChatGlobalModel>();
final TUIGroupListenerModel tuiGroupListenerModel = serviceLocator<TUIGroupListenerModel>();
tuiFriendShipViewModel.addFriendListener();
tuiConversationViewModel.setConversationListener();
tuiChatViewModel.addAdvancedMsgListener();
tuiGroupListenerModel.setGroupListener();
}
void removeListener() {
final TUIFriendShipViewModel tuiFriendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel = serviceLocator<TUIChatGlobalModel>();
final TUIGroupListenerModel tuiGroupListenerModel = serviceLocator<TUIGroupListenerModel>();
tuiFriendShipViewModel.removeFriendshipListener();
tuiConversationViewModel.removeConversationListener();
tuiChatViewModel.removeAdvanceMsgListener();
tuiGroupListenerModel.removeGroupListener();
}
callOnCallback(TIMCallback callbackValue) {
if (onCallback != null) {
Future.delayed(const Duration(milliseconds: 500), () {
onCallback!(callbackValue);
});
} else {
outputLogger.i("TUIKit Callback: ${callbackValue.type} - ${callbackValue.stackTrace}");
}
}
initDataModel() {
final TUIFriendShipViewModel tuiFriendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = serviceLocator<TUIConversationViewModel>();
tuiFriendShipViewModel.initFriendShipModel();
tuiConversationViewModel.initConversation();
}
clearData() {
final TUIFriendShipViewModel tuiFriendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
final TUIConversationViewModel tuiConversationViewModel = serviceLocator<TUIConversationViewModel>();
final TUIChatGlobalModel tuiChatViewModel = serviceLocator<TUIChatGlobalModel>();
tuiFriendShipViewModel.clearData();
tuiConversationViewModel.clearData();
tuiChatViewModel.clearData();
}
updateUserStatusList(List<V2TimUserStatus> newUserStatusList) {
try {
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
if (selfInfoViewModel.globalConfig?.isShowOnlineStatus == false) {
return;
}
final TUIFriendShipViewModel tuiFriendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
final currentUserStatusList = tuiFriendShipViewModel.userStatusList;
for (int i = 0; i < newUserStatusList.length; i++) {
final int indexInCurrentUserList =
currentUserStatusList.indexWhere((element) => element.userID == newUserStatusList[i].userID);
if (indexInCurrentUserList == -1) {
currentUserStatusList.add(newUserStatusList[i]);
} else {
currentUserStatusList[indexInCurrentUserList] = newUserStatusList[i];
}
}
tuiFriendShipViewModel.userStatusList = currentUserStatusList;
// ignore: empty_catches
} catch (e) {}
}
@override
Future<V2TimCallback> login({
required String userID,
required String userSig,
}) async {
_userID = userID;
_userSig = userSig;
V2TimCallback result = await TencentImSDKPlugin.v2TIMManager.login(userID: userID, userSig: userSig);
if (!PlatformUtils().isWeb) {
didLoginSuccess();
}
if (result.code != 0) {
callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorCode: result.code, errorMsg: result.desc));
}
return result;
}
void didLoginSuccess() async {
if (isLoginSuccess == true) {
return;
}
isLoginSuccess = true;
addInitListener();
initDataModel();
if (TencentUtils.checkString(_userID) == null) {
V2TimValueCallback<String> getLoginUserRes = await TencentImSDKPlugin.v2TIMManager.getLoginUser();
if (getLoginUserRes.code == 0) {
_userID = getLoginUserRes.data ?? "";
}
}
getUsersInfoWithRetry();
}
void getUsersInfoWithRetry() async {
V2TimValueCallback<List<V2TimUserFullInfo>>? res;
bool success = false;
while (!success) {
res = await getUsersInfo(userIDList: [_userID]);
if (res.code == 0 &&
res.data != null &&
res.data!.isNotEmpty &&
res.data!.firstWhereOrNull((element) => element.userID == _userID) != null) {
success = true;
} else {
await Future.delayed(const Duration(seconds: 2));
}
}
_loginInfo = res?.data!.firstWhereOrNull((element) => element.userID == _userID);
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
if (_loginInfo != null) {
selfInfoViewModel.setLoginInfo(_loginInfo);
}
}
// Deprecated
void didLoginOut() {
removeListener();
clearData();
_loginInfo = null;
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(_loginInfo);
}
@override
Future<V2TimCallback> logout() async {
final result = await TencentImSDKPlugin.v2TIMManager.logout();
isLoginSuccess = false;
removeListener();
clearData();
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(null);
return result;
}
@override
Future<V2TimCallback> logoutWithoutClearData() async {
final result = await TencentImSDKPlugin.v2TIMManager.logout();
isLoginSuccess = false;
removeListener();
serviceLocator<TUISelfInfoViewModel>().setLoginInfo(null);
return result;
}
@override
Future unInit() async {
final result = await TencentImSDKPlugin.v2TIMManager.unInitSDK();
return result;
}
@override
Future<V2TimValueCallback<List<V2TimUserFullInfo>>> getUsersInfo({
required List<String> userIDList,
}) {
return TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: userIDList);
}
@override
Future<V2TimCallback> setOfflinePushConfig({
required String token,
bool isTPNSToken = false,
int? businessID,
}) {
return TencentImSDKPlugin.v2TIMManager.getOfflinePushManager().setOfflinePushConfig(
businessID: businessID?.toDouble() ?? 0,
token: token,
isTPNSToken: isTPNSToken,
);
}
@override
Future<V2TimCallback> setSelfInfo({
required V2TimUserFullInfo userFullInfo,
}) {
return TencentImSDKPlugin.v2TIMManager.setSelfInfo(userFullInfo: userFullInfo);
}
@override
setTheme({required TUITheme theme}) {
// 合并传入Theme和默认Theme
final TUIThemeViewModel _theme = serviceLocator<TUIThemeViewModel>();
Map<String, Color?> jsonMap = Map.from(CommonColor.defaultTheme.toJson());
Map<String, Color?> jsonInputThemeMap = Map.from(theme.toJson());
jsonInputThemeMap.forEach((key, value) {
if (value != null) {
jsonMap.update(key, (v) => value);
}
});
_theme.theme = TUITheme.fromJson(jsonMap);
}
@override
setDarkTheme() {
final TUIThemeViewModel _theme = serviceLocator<TUIThemeViewModel>();
_theme.theme = TUITheme.dark; //Dark
}
@override
setLightTheme() {
final TUIThemeViewModel _theme = serviceLocator<TUIThemeViewModel>();
_theme.theme = TUITheme.light; //Light
}
@override
Future<V2TimCallback> setOfflinePushStatus({required AppStatus status, int? totalCount}) {
if (status == AppStatus.foreground) {
return TencentImSDKPlugin.v2TIMManager.getOfflinePushManager().doForeground();
} else {
return TencentImSDKPlugin.v2TIMManager.getOfflinePushManager().doBackground(unreadCount: totalCount ?? 0);
}
}
@override
setDeviceType(DeviceType deviceType) {
TUIKitScreenUtils.deviceType = deviceType;
}
}
@@ -0,0 +1,55 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitConfig {
/// Control if show online status of friends or contacts.
/// This only works with [Ultimate Edition].
/// [Default]: true.
final bool isShowOnlineStatus;
/// Controls if allows to check the disk memory after login.
/// If the storage space is less than 1GB,
/// an callback from `onTUIKitCallbackListener` will be invoked,
/// type is `INFO`, while code is 6661403.
final bool isCheckDiskStorageSpace;
/// The asset path of the default avatar image.
final String? defaultAvatarAssetPath;
/// The configuration of border radius for all the avatar shows in TUIKit.
final BorderRadius? defaultAvatarBorderRadius;
/// You can use this function to customize the Modal that shows on desktop.
/// Do not specified or return `false` will use our default implementation.
final Future<bool> Function(
TUIKitWideModalOperationKey operationKey,
BuildContext context,
Widget Function(VoidCallback closeFunc) child,
TUITheme? theme,
double? width,
double? height,
Offset? offset,
String? initText,
BorderRadius? borderRadius,
bool? isDarkBackground,
String? title,
VoidCallback? onSubmit,
Widget? submitWidget,
VoidCallback? onConfirm,
VoidCallback? onCancel,
)? showDesktopModalFunc;
/// Determines whether TUIKit should preload some messages after initialization for faster message display,
/// with a default value of `true`, and backward-compatibility.
final bool isPreloadMessagesAfterInit;
const TIMUIKitConfig( {
this.defaultAvatarAssetPath,
this.showDesktopModalFunc,
this.isPreloadMessagesAfterInit = true,
this.defaultAvatarBorderRadius,
this.isCheckDiskStorageSpace = true,
this.isShowOnlineStatus = true,
});
}
@@ -0,0 +1,43 @@
enum TUIKitWideModalOperationKey{
/// You could use this value for your own Modal usage.
custom,
// The following values are used in TUIKit
conversationSecondaryMenu,
chooseCountry,
beforeSendScreenShot,
showUserProfileFromChat,
addNewContact,
showBlockedUsers,
chooseContacts,
addFriend,
addGroup,
chooseGroupType,
settings,
contactUs,
aboutUs,
showConditionsAndTerms,
secondaryClickUserAvatar,
forward,
messageReadDetails,
mergerMessageList,
chooseMentionedMembers,
chatHistory,
groupAddOpt,
setMute,
setUnmute,
setAdmins,
deleteAdmin,
groupMembersList,
addGroupMembers,
kickOffGroupMembers,
confirmDeleteMessages,
confirmClearChatHistory,
confirmExitGroup,
confirmDisbandGroup,
confirmGeneral,
unableToSendDueToFolders
}
@@ -0,0 +1,4 @@
class TUIKitWebSupport {
static void addSetterToWindow() {}
static void addIdentifierToWindow() {}
}
@@ -0,0 +1,12 @@
// ignore: avoid_web_libraries_in_flutter
import 'dart:js';
class TUIKitWebSupport {
static void addSetterToWindow() {}
static void addIdentifierToWindow() {
// var object = newObject();
// setProperty(object, "platform", "flutter_uikit");
context['tencent_cloud_im_csig_flutter_for_web_25F_cy'] =
"flutter_web_uikit";
}
}
@@ -0,0 +1,94 @@
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_response_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_application_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_application_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_check_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_check_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_status.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
abstract class FriendshipServices {
Future<List<V2TimFriendInfoResult>?> getFriendsInfo({
required List<String> userIDList,
});
Future<List<V2TimUserFullInfo>?> getUsersInfo({
required List<String> userIDList,
});
Future<List<V2TimFriendOperationResult>?> addToBlackList({
required List<String> userIDList,
});
Future<V2TimValueCallback<V2TimFriendOperationResult>> addFriend({
required String userID,
required FriendTypeEnum addType,
String? remark,
String? friendGroup,
String? addSource,
String? addWording,
});
Future<List<V2TimFriendOperationResult>?> deleteFromBlackList({
required List<String> userIDList,
});
Future<List<V2TimFriendOperationResult>?> deleteFromFriendList({
required List<String> userIDList,
required FriendTypeEnum deleteType,
});
Future<List<V2TimFriendInfo>?> getFriendList();
Future<List<V2TimFriendInfoResult>?> searchFriends({
required V2TimFriendSearchParam searchParam,
});
Future<List<V2TimFriendInfo>?> getBlackList();
Future<List<V2TimFriendCheckResult>?> checkFriend({
required List<String> userIDList,
required FriendTypeEnum checkType,
});
Future<void> addFriendListener({
required V2TimFriendshipListener listener,
});
Future<void> removeFriendListener({
V2TimFriendshipListener? listener,
});
Future<V2TimFriendApplicationResult?> getFriendApplicationList();
Future<V2TimFriendOperationResult?> acceptFriendApplication(
{required FriendResponseTypeEnum responseType, required FriendApplicationTypeEnum type, required String userID});
Future<V2TimFriendOperationResult?> refuseFriendApplication(
{required FriendApplicationTypeEnum type, required String userID});
Future<V2TimCallback> setFriendInfo({
required String userID,
String? friendRemark,
Map<String, String>? friendCustomInfo,
});
Future<List<V2TimUserStatus>> getUserStatus({
required List<String> userIDList,
});
}
@@ -0,0 +1,311 @@
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_response_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_application_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_application_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_check_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_check_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_status.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/error_message_converter.dart';
class FriendshipServicesImpl implements FriendshipServices {
final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>();
@override
Future<List<V2TimFriendInfoResult>?> getFriendsInfo({
required List<String> userIDList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().getFriendsInfo(userIDList: userIDList);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimUserFullInfo>?> getUsersInfo({
required List<String> userIDList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: userIDList);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimFriendOperationResult>?> addToBlackList({
required List<String> userIDList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().addToBlackList(userIDList: userIDList);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<V2TimValueCallback<V2TimFriendOperationResult>> addFriend({
required String userID,
required FriendTypeEnum addType,
String? remark,
String? friendGroup,
String? addSource,
String? addWording,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().addFriend(
userID: userID,
addType: addType,
remark: remark,
addWording: addWording,
friendGroup: friendGroup,
addSource: addSource,
);
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;
}
@override
Future<List<V2TimFriendOperationResult>?> deleteFromBlackList({
required List<String> userIDList,
}) async {
final res =
await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().deleteFromBlackList(userIDList: userIDList);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimFriendOperationResult>?> deleteFromFriendList({
required List<String> userIDList,
required FriendTypeEnum deleteType,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.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,
infoRecommendText: TIM_t("好友删除失败")));
return null;
}
}
@override
Future<List<V2TimFriendInfo>?> getFriendList() async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().getFriendList();
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimFriendInfo>?> getBlackList() async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().getBlackList();
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimFriendCheckResult>?> checkFriend({
required List<String> userIDList,
required FriendTypeEnum checkType,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getFriendshipManager()
.checkFriend(userIDList: userIDList, checkType: checkType);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<void> addFriendListener({
required V2TimFriendshipListener listener,
}) {
return TencentImSDKPlugin.v2TIMManager.getFriendshipManager().addFriendListener(listener: listener);
}
@override
Future<void> removeFriendListener({
V2TimFriendshipListener? listener,
}) {
return TencentImSDKPlugin.v2TIMManager.getFriendshipManager().removeFriendListener(listener: listener);
}
@override
Future<V2TimFriendApplicationResult?> getFriendApplicationList() async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().getFriendApplicationList();
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<V2TimFriendOperationResult?> acceptFriendApplication({
required FriendResponseTypeEnum responseType,
required FriendApplicationTypeEnum type,
required String userID,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().acceptFriendApplication(
responseType: responseType,
type: type,
userID: userID,
);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<V2TimFriendOperationResult?> refuseFriendApplication(
{required FriendApplicationTypeEnum type, required String userID}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getFriendshipManager()
.refuseFriendApplication(type: type, userID: userID);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<V2TimCallback> setFriendInfo({
required String userID,
String? friendRemark,
Map<String, String>? friendCustomInfo,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getFriendshipManager()
.setFriendInfo(friendRemark: friendRemark, friendCustomInfo: friendCustomInfo, userID: userID);
if (res.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
return res;
}
@override
Future<List<V2TimFriendInfoResult>?> searchFriends({
required V2TimFriendSearchParam searchParam,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getFriendshipManager().searchFriends(searchParam: searchParam);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimUserStatus>> getUserStatus({
required List<String> userIDList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getUserStatus(userIDList: userIDList);
if (res.code == 0) {
return res.data ?? [];
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return [];
}
}
}
+121
View File
@@ -0,0 +1,121 @@
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_filter_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_role_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_application_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_application_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
abstract class GroupServices {
Future<List<V2TimGroupInfo>?> getJoinedGroupList();
Future<List<V2TimGroupInfoResult>?> getGroupsInfo({
required List<String> groupIDList,
});
Future<V2TimValueCallback<V2TimGroupMemberInfoResult>> getGroupMemberList({
required String groupID,
required GroupMemberFilterTypeEnum filter,
required String nextSeq,
int count = 15,
int offset = 0,
});
Future<V2TimValueCallback<List<V2TimGroupMemberFullInfo>>> getGroupMembersInfo(
{required String groupID, required List<String> memberList});
Future<V2TimCallback> setGroupInfo({
required V2TimGroupInfo info,
});
Future<V2TimCallback> setGroupMemberRole({
required String groupID,
required String userID,
required GroupMemberRoleTypeEnum role,
});
getGroupMembersInfoThrottle({required String groupID, required List<String> memberList, Function? callBack});
Future<V2TimCallback> muteGroupMember({
required String groupID,
required String userID,
required int seconds,
});
Future<V2TimCallback> setGroupMemberInfo({
required String groupID,
required String userID,
String? nameCard,
Map<String, String>? customInfo,
});
Future<V2TimCallback> kickGroupMember({
required String groupID,
required List<String> memberList,
String? reason,
});
Future<V2TimValueCallback<List<V2TimGroupMemberOperationResult>>> inviteUserToGroup({
required String groupID,
required List<String> userList,
});
Future<V2TimValueCallback<List<V2TimGroupInfo>>> searchGroups({
required V2TimGroupSearchParam searchParam,
});
Future<V2TimValueCallback<V2GroupMemberInfoSearchResult>> searchGroupMembers({
required V2TimGroupMemberSearchParam searchParam,
});
Future<V2TimCallback> joinGroup({
required String groupID,
required String message,
});
Future<void> addGroupListener({
required V2TimGroupListener listener,
});
Future<void> removeGroupListener({
V2TimGroupListener? listener,
});
Future<V2TimValueCallback<V2TimGroupApplicationResult>> getGroupApplicationList();
Future<V2TimCallback> acceptGroupApplication({
required String groupID,
required String fromUser,
required String toUser,
required int addTime,
required int type,
String? reason,
});
Future<V2TimCallback> refuseGroupApplication(
{String? reason,
required int addTime,
required String groupID,
required String fromUser,
required String toUser,
required GroupApplicationTypeEnum type});
}
@@ -0,0 +1,321 @@
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_filter_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_role_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_application_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_application_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/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';
class GroupServicesImpl extends GroupServices {
static List<Function?> groupInfoCallBackList = [];
final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>();
final throttleGetGroupInfo = OptimizeUtils.throttle((val) async {
String groupID = val["groupID"];
List<String> memberList = val["memberList"];
final res = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.getGroupMembersInfo(groupID: groupID, memberList: memberList);
emitGroupCbList(res.data ?? []);
clearGroupCbList();
}, 1000);
static emitGroupCbList(List<V2TimGroupMemberFullInfo?> list) {
for (var cb in groupInfoCallBackList) {
cb!(list);
}
}
static clearGroupCbList() {
groupInfoCallBackList = [];
}
@override
Future<List<V2TimGroupInfo>?> getJoinedGroupList() async {
final res = await TencentImSDKPlugin.v2TIMManager.getGroupManager().getJoinedGroupList();
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
Future<List<V2TimGroupInfoResult>?> getGroupsInfo({
required List<String> groupIDList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getGroupManager().getGroupsInfo(groupIDList: groupIDList);
if (res.code == 0) {
return res.data;
} else {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
}
@override
void getGroupMembersInfoThrottle(
{required String groupID, required List<String> memberList, Function? callBack}) async {
if (callBack != null) {
groupInfoCallBackList.add(callBack);
throttleGetGroupInfo({"groupID": groupID, "memberList": memberList});
}
}
@override
Future<V2TimValueCallback<List<V2TimGroupMemberFullInfo>>> getGroupMembersInfo(
{required String groupID, required List<String> memberList}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.getGroupMembersInfo(groupID: groupID, memberList: memberList);
if (res.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
return res;
}
@override
Future<V2TimValueCallback<V2TimGroupMemberInfoResult>> getGroupMemberList({
required String groupID,
required GroupMemberFilterTypeEnum filter,
required String nextSeq,
int count = 15,
int offset = 0,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.getGroupMemberList(groupID: groupID, filter: filter, nextSeq: nextSeq, count: count, offset: offset);
if (res.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
return res;
}
@override
Future<V2TimCallback> setGroupInfo({
required V2TimGroupInfo info,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getGroupManager().setGroupInfo(info: info);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> setGroupMemberRole({
required String groupID,
required String userID,
required GroupMemberRoleTypeEnum role,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.setGroupMemberRole(groupID: groupID, userID: userID, role: role);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> muteGroupMember({
required String groupID,
required String userID,
required int seconds,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.muteGroupMember(groupID: groupID, userID: userID, seconds: seconds);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> setGroupMemberInfo({
required String groupID,
required String userID,
String? nameCard,
Map<String, String>? customInfo,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.setGroupMemberInfo(groupID: groupID, userID: userID, nameCard: nameCard, customInfo: customInfo);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> kickGroupMember({
required String groupID,
required List<String> memberList,
String? reason,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getGroupManager()
.kickGroupMember(groupID: groupID, memberList: memberList, reason: reason);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimValueCallback<List<V2TimGroupMemberOperationResult>>> inviteUserToGroup({
required String groupID,
required List<String> userList,
}) async {
final result =
await TencentImSDKPlugin.v2TIMManager.getGroupManager().inviteUserToGroup(groupID: groupID, userList: userList);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimValueCallback<List<V2TimGroupInfo>>> searchGroups({
required V2TimGroupSearchParam searchParam,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getGroupManager().searchGroups(searchParam: searchParam);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> joinGroup({
required String groupID,
required String message,
}) async {
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,
infoRecommendText: recommendText));
}
return result;
}
@override
Future<V2TimValueCallback<V2GroupMemberInfoSearchResult>> searchGroupMembers({
required V2TimGroupMemberSearchParam searchParam,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getGroupManager().searchGroupMembers(param: searchParam);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<void> addGroupListener({
required V2TimGroupListener listener,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.addGroupListener(listener: listener);
return result;
}
@override
Future<void> removeGroupListener({
V2TimGroupListener? listener,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.removeGroupListener(listener: listener);
return result;
}
@override
Future<V2TimValueCallback<V2TimGroupApplicationResult>> getGroupApplicationList() async {
final result = await TencentImSDKPlugin.v2TIMManager.getGroupManager().getGroupApplicationList();
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> acceptGroupApplication(
{required String groupID,
required String fromUser,
required String toUser,
required int type,
required int addTime,
String? reason}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getGroupManager().acceptGroupApplication(
groupID: groupID,
fromUser: fromUser,
toUser: toUser,
addTime: addTime,
type: GroupApplicationTypeEnum.values[type],
reason: reason ?? "",
);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> refuseGroupApplication(
{String? reason,
required int addTime,
required String groupID,
required String fromUser,
required String toUser,
required GroupApplicationTypeEnum type}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getGroupManager().refuseGroupApplication(
groupID: groupID, fromUser: fromUser, toUser: toUser, type: type, addTime: addTime, reason: reason);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
}
@@ -0,0 +1,685 @@
// ignore_for_file: deprecated_member_use
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSimpleMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/get_group_message_read_member_list_filter.dart';
import 'package:tencent_cloud_chat_sdk/enum/history_msg_get_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_priority_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/offlinePushInfo.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_message_read_member_list.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_message_read_member_list.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_change_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_list_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_list_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_online_url.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_online_url.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_receipt.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_msg_create_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_msg_create_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.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';
class MessageServiceImpl extends MessageService {
final CoreServicesImpl _coreService = serviceLocator<CoreServicesImpl>();
final Map<String, List<V2TimMessage>> messageListMap = {};
final Map<String, List<V2TimMessage>> sendingMessage = {};
@override
Future<MessageListResponse> getHistoryMessageListV2({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq = -1,
required int count,
String? lastMsgID,
List<int>? messageTypeList,
}) async {
bool haveMoreData = true;
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().getHistoryMessageList(
count: count,
getType: getType,
userID: userID,
groupID: groupID,
lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq,
messageTypeList: messageTypeList);
final List<V2TimMessage> responseMessageList = res.data ?? [];
final conversationID = userID ?? groupID;
final cachedMessageList = messageListMap[conversationID];
List<V2TimMessage> combinedMessageList = [];
// 加载更多
if (lastMsgID != null && cachedMessageList != null) {
combinedMessageList = [...cachedMessageList, ...responseMessageList];
// 首次加载
} else {
final bool existSendingMessage =
sendingMessage[conversationID] != null && sendingMessage[conversationID]!.isNotEmpty;
// 存在未发送完成的消息
if (existSendingMessage) {
combinedMessageList = [...sendingMessage[conversationID]!, ...responseMessageList];
} else {
sendingMessage.remove(conversationID);
combinedMessageList = responseMessageList;
}
}
if (res.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
if (responseMessageList.isEmpty ||
(!PlatformUtils().isWeb && responseMessageList.length < count) ||
(PlatformUtils().isWeb && responseMessageList.length < min(count, 20))) {
haveMoreData = false;
} else {
haveMoreData = true;
}
return MessageListResponse(haveMoreData: haveMoreData, data: combinedMessageList);
}
@override
Future<List<V2TimMessage>> getHistoryMessageList({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq = -1,
required int count,
String? lastMsgID,
List<int>? messageTypeList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().getHistoryMessageList(
count: count,
getType: getType,
userID: userID,
groupID: groupID,
lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq,
messageTypeList: messageTypeList);
final reponseMessageList = res.data ?? [];
if (res.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
return reponseMessageList;
}
@override
Future<V2TimMessageListResult?> getHistoryMessageListWithComplete({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq = 0,
required int count,
String? lastMsgID,
List<int>? messageTypeList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().getHistoryMessageListV2(
count: count,
getType: getType,
userID: userID,
groupID: groupID,
lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq,
messageTypeList: messageTypeList);
final responseMessageList = res.data;
if (res.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
}
return responseMessageList;
}
@override
Future addSimpleMsgListener({
required V2TimSimpleMsgListener listener,
}) async {
return TencentImSDKPlugin.v2TIMManager.addSimpleMsgListener(listener: listener);
}
@override
Future<void> removeSimpleMsgListener({V2TimSimpleMsgListener? listener}) {
return TencentImSDKPlugin.v2TIMManager.removeSimpleMsgListener(listener: listener);
}
@override
Future<void> addAdvancedMsgListener({
required V2TimAdvancedMsgListener listener,
}) {
return TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener(listener: listener);
}
@override
Future<V2TimValueCallback<V2TimGroupMessageReadMemberList>> getGroupMessageReadMemberList({
required String messageID,
required GetGroupMessageReadMemberListFilter filter,
int nextSeq = 0,
int count = 100,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.getGroupMessageReadMemberList(messageID: messageID, filter: filter, nextSeq: nextSeq, count: count);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimValueCallback<List<V2TimMessageReceipt>>> getMessageReadReceipts({
required List<String> messageIDList,
}) async {
final result =
await TencentImSDKPlugin.v2TIMManager.getMessageManager().getMessageReadReceipts(messageIDList: messageIDList);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> sendMessageReadReceipts({
required List<String> messageIDList,
}) async {
return _retryMarkMessageAsRead(action: () {
return TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessageReadReceipts(messageIDList: messageIDList);
});
}
@override
Future<V2TimMsgCreateInfoResult?> createTextMessage({required String text}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createTextMessage(text: text);
if (res.code == 0) {
final messageResult = res.data;
return messageResult;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createCustomMessage({required String data}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createCustomMessage(data: data);
if (res.code == 0) {
final messageResult = res.data;
return messageResult;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createFaceMessage({required int index, required String data}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createFaceMessage(index: index, data: data);
if (res.code == 0) {
final messageResult = res.data;
return messageResult;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimValueCallback<V2TimMessage>> reSendMessage({required String msgID, bool? onlineUserOnly}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.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, infoRecommendText: recommendText));
}
return res;
}
@override
Future<V2TimMsgCreateInfoResult?> createTextAtMessage(
{required String text, required List<String> atUserList}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createTextAtMessage(text: text, atUserList: atUserList);
if (res.code == 0) {
final messageResult = res.data;
return messageResult;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createImageMessage(
{String? imageName, String? imagePath, dynamic inputElement}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createImageMessage(imageName: imageName, imagePath: imagePath ?? "", inputElement: inputElement);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createSoundMessage({
required String soundPath,
required int duration,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createSoundMessage(soundPath: soundPath, duration: duration);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimValueCallback<V2TimMessage>> sendMessage({
required String id, // 自己创建的ID
required String receiver,
required String groupID,
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
bool onlineUserOnly = false,
bool isExcludedFromUnreadCount = false,
bool needReadReceipt = false,
OfflinePushInfo? offlinePushInfo,
String? cloudCustomData,
String? localCustomData,
bool isExcludedFromContentModeration = false,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendMessage(
id: id,
receiver: receiver,
groupID: groupID,
priority: priority,
onlineUserOnly: onlineUserOnly,
offlinePushInfo: offlinePushInfo,
needReadReceipt: needReadReceipt,
localCustomData: localCustomData,
cloudCustomData: cloudCustomData,
isExcludedFromContentModeration: isExcludedFromContentModeration,
);
if (result.code != 0) {
String recommendText = ErrorMessageConverter.getErrorMessage(result.code);
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: result.desc,
errorCode: result.code,
infoRecommendText: recommendText));
}
return result;
}
@override
Future<V2TimCallback> deleteMessageFromLocalStorage({
required String msgID,
Object? webMessageInstance,
}) async {
V2TimCallback result;
if (kIsWeb) {
result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.deleteMessages(msgIDs: [], webMessageInstanceList: [webMessageInstance]);
} else {
result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().deleteMessageFromLocalStorage(msgID: msgID);
}
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> revokeMessage({required String msgID, Object? webMessageInstance}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.revokeMessage(msgID: msgID, webMessageInstatnce: webMessageInstance);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> clearC2CHistoryMessage({
required String userID,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().clearC2CHistoryMessage(userID: userID);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> clearGroupHistoryMessage({
required String groupID,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().clearGroupHistoryMessage(groupID: groupID);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
Future<V2TimCallback> _retryMarkMessageAsRead({
required Future<V2TimCallback> Function() action,
int retries = 3,
}) async {
V2TimCallback result;
int attempts = 0;
do {
result = await action();
if (result.code == 0) {
return result;
}
attempts++;
await Future.delayed(const Duration(milliseconds: 500));
} while (attempts < retries);
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
return result;
}
@override
Future<V2TimCallback> markC2CMessageAsRead({
required String userID,
}) {
return _retryMarkMessageAsRead(action: () {
return TencentImSDKPlugin.v2TIMManager.getConversationManager().cleanConversationUnreadMessageCount(
conversationID: "${TUIConversationViewModel.conversationC2CPrefix}$userID",
cleanTimestamp: 0,
cleanSequence: 0,
);
});
}
@override
Future<V2TimCallback> markGroupMessageAsRead({
required String groupID,
}) {
return _retryMarkMessageAsRead(action: () {
return TencentImSDKPlugin.v2TIMManager.getConversationManager().cleanConversationUnreadMessageCount(
conversationID: "${TUIConversationViewModel.conversationGroupPrefix}$groupID",
cleanTimestamp: 0,
cleanSequence: 0,
);
});
}
@override
Future<void> removeAdvancedMsgListener({V2TimAdvancedMsgListener? listener}) async {
final result =
await TencentImSDKPlugin.v2TIMManager.getMessageManager().removeAdvancedMsgListener(listener: listener);
return result;
}
@override
Future<List<V2TimMessage>?> downloadMergerMessage({
required String msgID,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().downloadMergerMessage(msgID: msgID);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createForwardMessage({
required String msgID,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createForwardMessage(msgID: msgID);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createMergerMessage({
required List<String> msgIDList,
required String title,
required List<String> abstractList,
required String compatibleText,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createMergerMessage(
msgIDList: msgIDList, title: title, abstractList: abstractList, compatibleText: compatibleText);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimCallback> deleteMessages({required List<String> msgIDs, List<dynamic>? webMessageInstanceList}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.deleteMessages(msgIDs: msgIDs, webMessageInstanceList: webMessageInstanceList);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimMsgCreateInfoResult?> createVideoMessage(
{String? videoPath, String? type, int? duration, String? snapshotPath, dynamic inputElement}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().createVideoMessage(
videoFilePath: videoPath ?? "",
type: type ?? "",
duration: duration ?? 1,
snapshotPath: snapshotPath ?? "",
inputElement: inputElement);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimValueCallback<V2TimMessage>> sendReplyMessage({
required String id, // 自己创建的ID
required String receiver,
required String groupID,
OfflinePushInfo? offlinePushInfo,
bool needReadReceipt = false,
required V2TimMessage replyMessage, // 被回复的消息
}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().sendReplyMessage(
id: id,
receiver: receiver,
offlinePushInfo: offlinePushInfo,
groupID: groupID,
needReadReceipt: needReadReceipt,
replyMessage: replyMessage);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimMsgCreateInfoResult?> createFileMessage(
{String? filePath, required String fileName, dynamic inputElement}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createFileMessage(filePath: filePath ?? "", fileName: fileName, inputElement: inputElement);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimMsgCreateInfoResult?> createLocationMessage(
{required String desc, required double longitude, required double latitude}) async {
final res = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.createLocationMessage(desc: desc, longitude: longitude, latitude: latitude);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimValueCallback<V2TimMessageSearchResult>> searchLocalMessages(
{required V2TimMessageSearchParam searchParam}) async {
final result =
await TencentImSDKPlugin.v2TIMManager.getMessageManager().searchLocalMessages(searchParam: searchParam);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<List<V2TimMessage>?> findMessages({
required List<String> messageIDList,
}) async {
final res = await TencentImSDKPlugin.v2TIMManager.getMessageManager().findMessages(messageIDList: messageIDList);
if (res.code == 0) {
return res.data;
}
_coreService.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: res.desc, errorCode: res.code));
return null;
}
@override
Future<V2TimCallback> setLocalCustomInt({required String msgID, required int localCustomInt}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.setLocalCustomInt(msgID: msgID, localCustomInt: localCustomInt);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> setC2CReceiveMessageOpt({
required List<String> userIDList,
required ReceiveMsgOptEnum opt,
}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.setC2CReceiveMessageOpt(userIDList: userIDList, opt: opt);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> setGroupReceiveMessageOpt({
required String groupID,
required ReceiveMsgOptEnum opt,
}) async {
final result =
await TencentImSDKPlugin.v2TIMManager.getMessageManager().setGroupReceiveMessageOpt(groupID: groupID, opt: opt);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimValueCallback<V2TimMessageChangeInfo>> modifyMessage({required V2TimMessage message}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().modifyMessage(message: message);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> setLocalCustomData({required String msgID, required String localCustomData}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.setLocalCustomData(msgID: msgID, localCustomData: localCustomData);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimValueCallback<V2TimMessageOnlineUrl>> getMessageOnlineUrl({required String msgID}) async {
final result = await TencentImSDKPlugin.v2TIMManager.getMessageManager().getMessageOnlineUrl(msgID: msgID);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<V2TimCallback> downloadMessage(
{required String msgID, required int messageType, required int imageType, required bool isSnapshot}) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.downloadMessage(msgID: msgID, messageType: messageType, imageType: imageType, isSnapshot: isSnapshot);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result;
}
@override
Future<String> translateText(String text, String target) async {
final result =
await TencentImSDKPlugin.v2TIMManager.getMessageManager().translateText(texts: [text], targetLanguage: target);
if (result.code != 0) {
_coreService
.callOnCallback(TIMCallback(type: TIMCallbackType.API_ERROR, errorMsg: result.desc, errorCode: result.code));
}
return result.data?[text] ?? "";
}
}
@@ -0,0 +1,220 @@
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSimpleMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/get_group_message_read_member_list_filter.dart';
import 'package:tencent_cloud_chat_sdk/enum/history_msg_get_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_priority_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/offlinePushInfo.dart';
import 'package:tencent_cloud_chat_sdk/enum/receive_message_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_message_read_member_list.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_message_read_member_list.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_change_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_list_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_list_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_online_url.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_online_url.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_receipt.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_param.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_msg_create_info_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_msg_create_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
class MessageListResponse {
final bool haveMoreData;
final List<V2TimMessage> data;
MessageListResponse({required this.haveMoreData, required this.data});
}
abstract class MessageService {
Future<List<V2TimMessage>> getHistoryMessageList({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq,
required int count,
String? lastMsgID,
List<int>? messageTypeList,
});
Future<V2TimMessageListResult?> getHistoryMessageListWithComplete({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq,
required int count,
String? lastMsgID,
List<int>? messageTypeList,
});
Future<MessageListResponse> getHistoryMessageListV2({
HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_LOCAL_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq,
required int count,
String? lastMsgID,
List<int>? messageTypeList,
});
Future addSimpleMsgListener({
required V2TimSimpleMsgListener listener,
});
Future addAdvancedMsgListener({
required V2TimAdvancedMsgListener listener,
});
Future<void> removeSimpleMsgListener({V2TimSimpleMsgListener? listener});
Future<V2TimMsgCreateInfoResult?> createTextMessage({required String text});
Future<V2TimMsgCreateInfoResult?> createFaceMessage({required int index, required String data});
Future<V2TimMsgCreateInfoResult?> createCustomMessage({required String data});
Future<V2TimMsgCreateInfoResult?> createTextAtMessage({required String text, required List<String> atUserList});
Future<V2TimValueCallback<V2TimMessage>> sendMessage(
{required String id, // 自己创建的ID
required String receiver,
required String groupID,
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
bool onlineUserOnly = false,
bool isExcludedFromUnreadCount = false,
bool needReadReceipt = false,
OfflinePushInfo? offlinePushInfo,
String? cloudCustomData, // 云自定义消息字段,只能在消息发送前添加
String? localCustomData,
bool isExcludedFromContentModeration});
Future<V2TimValueCallback<V2TimMessage>> sendReplyMessage({
required String id, // 自己创建的ID
required String receiver,
required String groupID,
OfflinePushInfo? offlinePushInfo,
bool needReadReceipt = false,
required V2TimMessage replyMessage, // 被回复的消息
});
Future<V2TimValueCallback<V2TimMessage>> reSendMessage({required String msgID, bool onlineUserOnly});
Future<V2TimValueCallback<V2TimMessageChangeInfo>> modifyMessage({required V2TimMessage message});
Future<V2TimMsgCreateInfoResult?> createImageMessage({String? imageName, String? imagePath, dynamic inputElement});
Future<V2TimMsgCreateInfoResult?> createVideoMessage(
{String? videoPath = "", String? type = "", int? duration = 0, String? snapshotPath = "", dynamic inputElement});
Future<V2TimMsgCreateInfoResult?> createFileMessage(
{String? filePath, required String fileName, dynamic inputElement});
Future<V2TimMsgCreateInfoResult?> createLocationMessage(
{required String desc, required double longitude, required double latitude});
Future<V2TimMsgCreateInfoResult?> createSoundMessage({
required String soundPath,
required int duration,
});
Future<V2TimMsgCreateInfoResult?> createForwardMessage({
required String msgID,
});
Future<V2TimMsgCreateInfoResult?> createMergerMessage({
required List<String> msgIDList,
required String title,
required List<String> abstractList,
required String compatibleText,
});
Future<V2TimCallback> deleteMessageFromLocalStorage({
required String msgID,
Object? webMessageInstance,
});
Future<V2TimCallback> revokeMessage({required String msgID, Object? webMessageInstance});
Future<V2TimCallback> clearC2CHistoryMessage({
required String userID,
});
Future<V2TimCallback> clearGroupHistoryMessage({
required String groupID,
});
Future<V2TimCallback> markC2CMessageAsRead({
required String userID,
});
Future<V2TimCallback> markGroupMessageAsRead({
required String groupID,
});
Future<void> removeAdvancedMsgListener({V2TimAdvancedMsgListener? listener});
Future<List<V2TimMessage>?> downloadMergerMessage({
required String msgID,
});
Future<V2TimCallback> deleteMessages({required List<String> msgIDs, List<dynamic>? webMessageInstanceList});
Future<List<V2TimMessage>?> findMessages({
required List<String> messageIDList,
});
Future<V2TimValueCallback<V2TimMessageSearchResult>> searchLocalMessages({
required V2TimMessageSearchParam searchParam,
});
Future<V2TimCallback> setLocalCustomInt({required String msgID, required int localCustomInt});
Future<V2TimCallback> setLocalCustomData({required String msgID, required String localCustomData});
Future<V2TimCallback> setC2CReceiveMessageOpt({
required List<String> userIDList,
required ReceiveMsgOptEnum opt,
});
Future<V2TimCallback> setGroupReceiveMessageOpt({
required String groupID,
required ReceiveMsgOptEnum opt,
});
Future<V2TimValueCallback<V2TimGroupMessageReadMemberList>> getGroupMessageReadMemberList({
required String messageID,
required GetGroupMessageReadMemberListFilter filter,
int nextSeq = 0,
int count = 100,
});
Future<V2TimValueCallback<List<V2TimMessageReceipt>>> getMessageReadReceipts({
required List<String> messageIDList,
});
Future<V2TimCallback> sendMessageReadReceipts({
required List<String> messageIDList,
});
Future<V2TimValueCallback<V2TimMessageOnlineUrl>> getMessageOnlineUrl({
required String msgID,
});
Future<V2TimCallback> downloadMessage({
required String msgID,
required int messageType,
required int imageType, // 图片类型,仅messageType为图片消息是有效
required bool isSnapshot, // 是否是视频封面,仅messageType为视频消息是有效
});
Future<String> translateText(String text, String target);
}
+55
View File
@@ -0,0 +1,55 @@
import 'package:get_it/get_it.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/listener_model/tui_group_listener_model.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_friendship_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_setting_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/friendShip/friendship_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services_implement.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_service_implement.dart';
import 'package:tencent_cloud_chat_uikit/data_services/message/message_services.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_search_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/theme/tui_theme_view_model.dart';
final serviceLocator = GetIt.instance;
bool boolIsInitailized = false;
void setupServiceLocator() {
if (!boolIsInitailized) {
// setting
serviceLocator.registerSingleton<TUISettingModel>(TUISettingModel());
// services
serviceLocator.registerSingleton<CoreServicesImpl>(CoreServicesImpl());
serviceLocator
.registerSingleton<TUISelfInfoViewModel>(TUISelfInfoViewModel());
serviceLocator
.registerSingleton<ConversationService>(ConversationServicesImpl());
serviceLocator.registerSingleton<MessageService>(MessageServiceImpl());
serviceLocator
.registerSingleton<FriendshipServices>(FriendshipServicesImpl());
serviceLocator.registerSingleton<GroupServices>(GroupServicesImpl());
// view models
serviceLocator.registerSingleton<TUIChatGlobalModel>(TUIChatGlobalModel());
serviceLocator.registerSingleton<TUIChatModelTools>(TUIChatModelTools());
serviceLocator.registerSingleton<TUIConversationViewModel>(
TUIConversationViewModel());
serviceLocator
.registerSingleton<TUIFriendShipViewModel>(TUIFriendShipViewModel());
serviceLocator.registerSingleton<TUIThemeViewModel>(TUIThemeViewModel());
serviceLocator.registerSingleton<TUISearchViewModel>(TUISearchViewModel());
// listener models
serviceLocator
.registerSingleton<TUIGroupListenerModel>(TUIGroupListenerModel());
boolIsInitailized = true;
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+702
View File
@@ -0,0 +1,702 @@
{
"k_1yemzyd": "收到一條訊息",
"k_0ylosxn": "自定義訊息",
"k_13sajrj": "貼圖訊息",
"k_13sjeb7": "檔案訊息",
"k_0yd2ft8": "群提示訊息",
"k_13s7mxn": "圖片訊息",
"k_13satlt": "位置訊息",
"k_00bbtsx": "合並轉發訊息",
"k_13sqwu4": "語音訊息",
"k_13sqjjp": "影片",
"k_1fdhj9g": "該版本不支持此訊息",
"k_06pujtm": "同意任何用戶添加好友",
"k_0gyhkp5": "需要驗證",
"k_121ruco": "拒絕任何人加好友",
"k_05nspni": "自定義字段",
"k_03fchyy": "群頭像",
"k_03i9mfe": "群簡介",
"k_03agq58": "群名稱",
"k_039xqny": "群通知",
"k_003tr0a": "群主",
"k_03iqsh4": "$s為 ",
"k_191t5n4": "$opUserNickName修改",
"k_1pg6aoj": "$opUserNickName退出群組",
"k_1f6zt3v": "邀請$invitedMemberString加入群組",
"k_0y7zd07": "將$invitedMemberString踢出群組",
"k_03c49qt": "去授權",
"k_1d5mshh": "用戶$joinedMemberString加入了群組",
"k_002wddw": "禁言",
"k_0got6f7": "解除禁言",
"k_0yenqf0": "$userName 被",
"k_0spotql": "將 $adminMember 設置為管理員",
"k_0pg5zzj": "系統訊息 $operationType",
"k_0ohzb9l": "通話時間:$callTime",
"k_1uaqed6": "[自定義]",
"k_0z2z7rx": "[語音]",
"k_0y39ngu": "[貼圖]",
"k_1c7z88n": "[檔案] $fileName",
"k_0y1a2my": "[圖片]",
"k_0z4fib8": "[影片]",
"k_0y24mcg": "[位置]",
"k_0pewpd1": "[聊天記錄]",
"k_13s8d9p": "未知訊息",
"k_1c3us5n": "當前群組不支持@全體成員",
"k_11k579v": "發言中有非法語句",
"k_003qkx2": "日歷",
"k_003n2pz": "相機",
"k_03idjo0": "聯絡人",
"k_003ltgm": "位置",
"k_02k3k86": "咪高風",
"k_003pm7l": "相冊",
"k_15ao57x": "相冊寫入",
"k_164m3jd": "本地存儲",
"k_0qba4ns": "想訪問您的$yoursItem",
"k_03r6qyx": "我們需要您的同意才能獲取信息",
"k_02noktt": "不允許",
"k_00043x4": "好",
"k_003qzac": "昨天",
"k_003r39d": "前天",
"k_03fqp9o": "星期天",
"k_03ibg5h": "星期一",
"k_03i7hu1": "星期二",
"k_03iaiks": "星期三",
"k_03el9pa": "星期四",
"k_03i7ok1": "星期五",
"k_03efxyg": "星期六",
"k_0oozw9x": "$diffMinutes 分鐘前",
"k_003q7ba": "下午",
"k_003q7bb": "上午",
"k_003pu3h": "現在",
"k_13hzn00": "昨天 $yesterday",
"k_0n9pyxz": "用戶不存在",
"k_1bjwemh": "搜尋用戶 ID",
"k_003kv3v": "搜尋",
"k_02owlq8": "我的用戶ID: $userID",
"k_1wu8h4x": "我是: $showName",
"k_16758qw": "添加好友",
"k_1shx4d9": "個性簽名: $selfSignature",
"k_0i553x0": "填寫驗證信息",
"k_031ocwx": "請填寫備註和分組",
"k_003ojje": "備註",
"k_003lsav": "分組",
"k_167bdvq": "我的好友",
"k_156b4ut": "好友申請已發送",
"k_002r305": "發送",
"k_03gu05e": "聊天室",
"k_03b4f3p": "會議群",
"k_03avj1p": "公開群",
"k_03asq2g": "工作群",
"k_03b3hbi": "未知群",
"k_1loix7s": "群類型: $groupType",
"k_1lqbsib": "該群組不存在",
"k_03h153m": "搜尋群ID",
"k_0oxak3r": "群申請已發送",
"k_002rflt": "刪除",
"k_1don84v": "無法定位到原訊息",
"k_003q5fi": "復製",
"k_003prq0": "轉發",
"k_002r1h2": "多選",
"k_003j708": "引用",
"k_003pqpr": "回收",
"k_03ezhho": "已復製",
"k_11ctfsz": "暫未實現",
"k_1hbjg5g": "[群系統訊息]",
"k_03tvswb": "[未知訊息]",
"k_155cj23": "您回收了一條訊息,",
"k_0gapun3": "重新編輯",
"k_1uh417q": "$displayName回收了一條訊息",
"k_1aszp2k": "您確定要重發這條訊息麽?",
"k_003rzap": "確定",
"k_003nevv": "取消",
"k_0003z7x": "您",
"k_002wfe4": "已讀",
"k_002wjlg": "未讀",
"k_0h1ygf8": "發起通話",
"k_0h169j0": "取消通話",
"k_0h13jjk": "接受通話",
"k_0h19hfx": "拒絕通話",
"k_0obi9lh": "超時未接聽",
"k_0y9u662": "「$appName」暫不可以開啟此類檔案,你可以使用其他應用開啟並預覽",
"k_001nmhu": "用其他應用開啟",
"k_1ht1b80": "正在接收中",
"k_105682d": "圖片載入失敗",
"k_0pytyeu": "圖片保存成功",
"k_0akceel": "圖片保存失敗",
"k_003rk1s": "保存",
"k_04a0awq": "[語音訊息]",
"k_105c3y3": "影片載入失敗",
"k_176rzr7": "聊天記錄",
"k_0d5z4m5": "選擇提醒人",
"k_003ngex": "完成",
"k_1665ltg": "發起呼叫",
"k_003n8b0": "拍攝",
"k_003kthh": "照片",
"k_003tnp0": "檔案",
"k_0jhdhtp": "發送失敗,影片不能大於100MB",
"k_119ucng": "圖片不能為空",
"k_0w9x8gw": "選擇成功$successPath",
"k_13dsw4l": "松開取消",
"k_0am7r68": "手指上滑,取消發送",
"k_15jl6qw": "說話時間太短!",
"k_0gx7vl6": "按住說話",
"k_15dlafd": "逐條轉發",
"k_15dryxy": "合並轉發",
"k_1eyhieh": "確定刪除已選訊息",
"k_17fmlyf": "清除聊天",
"k_0dhesoz": "取消置頂",
"k_002sk7x": "置頂",
"k_003ll77": "草稿",
"k_03icaxo": "自定義",
"k_1969986": "[語音通話]:$callingLastMsgShow",
"k_1960dlr": "[視訊通話]:$callingLastMsgShow",
"k_1np495n": "$messageString[有人@我]",
"k_1m797yi": "$messageString[@所有人]",
"k_1uaov41": "查找聊天內容",
"k_003kfai": "未知",
"k_13dq4an": "自動審批",
"k_0l13cde": "管理員審批",
"k_11y8c6a": "禁止加群",
"k_1kvyskd": "無網絡連接,無法修改",
"k_16payqf": "加群方式",
"k_0vzvn8r": "修改群名稱",
"k_038lh6u": "群管理",
"k_0k5wyiy": "設置管理員",
"k_0goiuwk": "全員禁言",
"k_1g889xx": "全員禁言開啟後,只允許群主和管理員發言。",
"k_0wlrefq": "添加需要禁言的群成員",
"k_0goox5g": "設置禁言",
"k_08daijh": "成功取消管理員身份",
"k_0bxm97s": "管理員 ($adminNum/10)",
"k_0k5u935": "添加管理員",
"k_03enyx5": "群成員",
"k_0jayw3z": "群成員($groupMemberNum人)",
"k_0h1svv1": "刪除群成員",
"k_0h1g636": "添加群成員",
"k_0uj7208": "無網絡連接,無法查看群成員",
"k_01yfa4o": "$memberCount人",
"k_0hpukyx": "查看更多群成員",
"k_0qtsar0": "訊息免打擾",
"k_0ef2a12": "修改我的群昵稱",
"k_1aajych": "僅限中文、字母、數字和下劃線,2-20個字",
"k_137pab5": "我的群昵稱",
"k_0ivim6d": "暫無群公告",
"k_03eq6cn": "群公告",
"k_002vxya": "編輯",
"k_17fpl3y": "置頂聊天",
"k_03es1ox": "群類型",
"k_003mz1i": "同意",
"k_003lpre": "拒絕",
"k_003qk66": "頭像",
"k_003lhvk": "昵稱",
"k_003ps50": "賬號",
"k_15lx52z": "個性簽名",
"k_003qgkp": "性別",
"k_003m6hr": "生日",
"k_0003v6a": "男",
"k_00043x2": "女",
"k_03bcjkv": "未設置",
"k_11s0gdz": "修改昵稱",
"k_0p3j4sd": "僅限中字、字母、數字和下劃線",
"k_15lyvdt": "修改簽名",
"k_0vylzjp": "這個人很懶,什麽也沒寫",
"k_1hs7ese": "等上線再改這個",
"k_03exjk7": "備註名",
"k_0s3skfd": "加入黑名單",
"k_0p3b31s": "修改備註名",
"k_0003y9x": "無",
"k_11zgnfs": "個人資料",
"k_03xd79d": "個性簽名: $signature",
"k_1tez2xl": "暫無個性簽名",
"k_118prbn": "全局搜尋",
"k_1m9dftc": "全部聯絡人",
"k_0em4gyz": "全部群組",
"k_002twmj": "群組",
"k_09kga0d": "更多聊天記錄",
"k_1ui5lzi": "$count條相關聊天記錄",
"k_09khmso": "相關聊天記錄",
"k_1kevf4k": "與$receiver的聊天記錄",
"k_0vjj2kp": "群組的聊天記錄",
"k_003n2rp": "選擇",
"k_03ignw6": "所有人",
"k_03erpei": "管理員",
"k_0qi9tno": "群主、管理員",
"k_1m9exwh": "最近聯絡人",
"k_119nwqr": "輸入不能為空",
"k_0pzwbmg": "影片保存成功",
"k_0aktupv": "影片保存失敗",
"k_1qbg9xc": "$option8為 ",
"k_1wq5ubm": "$option7修改",
"k_0y5pu80": "$option6退出群組",
"k_0nl7cmd": "邀請$option5加入群組",
"k_1ju5iqw": "將$option4踢出群組",
"k_1ovt677": "用戶$option3加入了群組",
"k_0k05b8b": "$option2 被",
"k_0wm4xeb": "系統訊息 $option2",
"k_0nbq9v3": "通話時間:$option2",
"k_0i1kf53": "[檔案] $option2",
"k_1gnnby6": "想訪問您的$option2",
"k_1wh4atg": "$option2 分鐘前",
"k_07sh7g1": "昨天 $option2",
"k_1pj8xzh": "我的用戶ID: $option2",
"k_0py1evo": "個性簽名: $option2",
"k_1kvj4i2": "$option2回收了一條訊息",
"k_1v0lbpp": "「$option2」暫不可以開啟此類檔案,你可以使用其他應用開啟並預覽",
"k_0torwfz": "選擇成功$option2",
"k_0i1bjah": "$option1回收了一條訊息",
"k_1qzxh9q": "通話時間:$option3",
"k_0wrgmom": "[語音通話]:$option1",
"k_06ix2f0": "[視訊通話]:$option2",
"k_08o3z5w": "[檔案] $option1",
"k_0ezbepg": "$option2[有人@我]",
"k_1ccnht1": "$option2[@所有人]",
"k_1k3arsw": "管理員 ($option2/10)",
"k_1d4golg": "群成員($option1人)",
"k_1bg69nt": "$option1人",
"k_00gjqxj": "個性簽名: $option1",
"k_0c29cxr": "$option1條相關聊天記錄",
"k_1twk5rz": "與$option1的聊天記錄",
"k_1vn4xq1": "將 $adminMember 取消管理員",
"k_0e35hsw": "為方便您將所拍攝的照片或影片發送給朋友,以及進行視訊通話,請允許我們訪問攝像頭進行拍攝照片和影片。",
"k_0dj6yr7": "為方便您發送語音訊息、拍攝影片以及音視訊通話,請允許我們使用咪高風進行錄音。",
"k_003qnsl": "存儲",
"k_0s3rtpw": "為方便您查看和選擇相冊裏的圖片影片發送給朋友,以及保存內容到設備,請允許我們訪問您設備上的照片、媒體內容。",
"k_0tezv85": " 申請獲取$option2",
"k_002rety": "權限",
"k_18o68ro": "需要授予",
"k_1onpf8u": " 相機權限,以正常使用拍攝圖片/影片、視訊通話等功能。",
"k_17irga5": " 咪高風權限,以正常使用發送語音訊息、拍攝影片、音視訊通話等功能。",
"k_0572kc4": " 訪問照片權限,以正常使用發送圖片、影片等功能。",
"k_0slykws": " 訪問相冊寫入權限,以正常使用存儲圖片、影片等功能。",
"k_119pkcd": " 檔案讀寫權限,以正常使用在聊天功能中的圖片查看、選擇能力和發送檔案的能力。",
"k_0gqewd3": "以後再說",
"k_03eq4s1": "去開啟",
"k_0nt2uyg": "回到最新位置",
"k_04l16at": "$option1條新訊息",
"k_13p3w93": "有人@我",
"k_18w5uk6": "@所有人",
"k_0jmujgh": "其他檔案正在接收中",
"k_12s5ept": "訊息詳情",
"k_0mxa4f4": "$option1人已讀",
"k_061tue3": "$option2人未讀",
"k_18qjstb": "轉讓群主",
"k_0on1aj2": "有$option2條@我訊息",
"k_09j4izl": "[有人@我] ",
"k_1oqtjw0": "[@所有人] ",
"k_1x5a9vb": "我是: $option1",
"k_14n31e7": "進群請求",
"k_08nc5j1": "群類型: $option1",
"k_1josu12": "$option1 條入群請求",
"k_0n2x5s0": "驗證消息: $option2",
"k_03c1nx0": "已同意",
"k_03aw9w8": "已拒絕",
"k_038ryos": "去處理",
"k_0gw8pum": "進群申請",
"k_1gcvfrj": "請填寫備註名",
"k_002v9zj": "确认",
"k_10oqrki": "轻触拍照",
"k_0f8b3ws": "加载失败",
"k_11cm5lm": "手动聚焦",
"k_002uzrd": "预览",
"k_003qkn3": "录像",
"k_003k6a7": "拍照",
"k_0bqpqco": "拍照按钮",
"k_1626ozl": "停止录像",
"k_003lvmu": "前置",
"k_003lued": "后置",
"k_003lwzh": "外置",
"k_002qzi3": "关闭",
"k_003pufb": "自动",
"k_0apm0ze": "拍照时闪光",
"k_157zog5": "始终闪光",
"k_0cfyqhy": "$option1 画面预览",
"k_0phctlz": "闪光模式: $option2",
"k_02vfqe0": "切换至 $option3 摄像头",
"k_0f0y9ex": "说话时间太短",
"k_0ln70tk": "无法打开URL",
"k_11a3jdv": "轻触拍照,长按摄像",
"k_1k18miv": "请传入离开群组生命周期函数,提供返回首页或其他页面的导航方法。",
"k_1fu9ahv": "全员禁言状态",
"k_0gmwbnd": "全员禁言中",
"k_0got2zr": "您被禁言",
"k_0y9jck8": "你必须自定义search bar,并处理点击跳转",
"k_0yum3tv": "如使用自定义区域,请在profileWidgetBuilder传入对应组件",
"k_09kalj0": "清空聊天记录",
"k_14j5iul": "删除并退出",
"k_125ru1w": "解散该群",
"k_0jtutmw": "退出后不会接收到此群聊消息",
"k_0jtzmqa": "解散后不会接收到此群聊消息",
"k_0r8fi93": "好友添加成功",
"k_02qw14e": "好友申请已发出",
"k_0n3md5x": "当前用户在黑名单",
"k_094phq4": "好友添加失败",
"k_129scag": "好友删除成功",
"k_129uzfn": "好友删除失败",
"k_1666isy": "清除好友",
"k_1679vrd": "加为好友",
"k_1ualc52": "看看对方带来的数据是啥",
"k_0szluvp": "设置对方在线状态",
"k_0f4rnf8": "该用户已是好友",
"k_1tdkom4": "您已是群成员",
"k_1p2lyuz": "对方正在输入中...",
"k_1g8wfpy": "...共$option1人",
"k_12rv9vw": "回应详情",
"k_0havgi0": "[查看详情 >>](${linkMessage.link})",
"k_0n9p7g8": "群组不存在",
"k_1tdh5vn": "您不是群成员",
"k_0h1q57v": "暂无群成员",
"k_0y5drq1": "[查看详情 >>]($option1)",
"k_03pjp61": "[表情消息]",
"k_1jpvzul": "[自定义消息]",
"k_03u3bh1": "[文件消息]",
"k_1odsnsw": "[群消息]",
"k_03sel4t": "[图片消息]",
"k_03sfw3r": "[位置消息]",
"k_03xpuwq": "[合并消息]",
"k_07ycxwo": "[没有元素]",
"k_03rc9vz": "[文本消息]",
"k_046uopf": "[视频消息]",
"k_0ehmsun": "设备存储空间不足,建议清理,以获得更好使用体验",
"k_003kmos": "图片",
"k_002s86q": "视频",
"k_06bk5ei": "视频消息仅限 mp4 格式",
"k_13opfxf": "Web网页端不支持搜索",
"k_1i0o0y2": "暂时仅限 Android/iOS 端",
"k_045dtzl": "$option1的聊天记录",
"k_0t0131u": "群资料信息",
"k_18ok8xz": "消息接收方式",
"k_03ax3ks": "群资料",
"k_0sqvoqo": "将 $option1 设置为管理员",
"k_1gbg1v8": "将 $option1 取消管理员",
"k_17k64g4": "群聊创建成功!",
"k_05mn217": "暂未安装表情包插件,如需使用表情相关功能,请根据本文档安装:https://cloud.tencent.com/document/product/269/70746",
"k_14j17nz": "暂无表情包",
"k_0fvjexh": "正在下载中",
"k_1cdagzz": "已加入待下载队列,其他文件下载中",
"k_0g4vojc": "开始下载",
"k_1g32es3": "[调皮]@2x.png",
"k_1g8qorz": "[爱你]@2x.png",
"k_1g4hmx6": "[爱情]@2x.png",
"k_1g6b558": "[爱心]@2x.png",
"k_1g3m4su": "[傲慢]@2x.png",
"k_1g2jym7": "[白眼]@2x.png",
"k_0cgkxuw": "[棒棒糖]@2x.png",
"k_1g48br2": "[抱抱]@2x.png",
"k_1g49ol8": "[抱拳]@2x.png",
"k_1g0ras3": "[爆筋]@2x.png",
"k_1ghy881": "[鄙视]@2x.png",
"k_1g86bmv": "[闭嘴]@2x.png",
"k_1g1xs1p": "[鞭炮]@2x.png",
"k_1g8i6ri": "[便便]@2x.png",
"k_1g2u5kf": "[擦汗]@2x.png",
"k_1g60uwh": "[彩带]@2x.png",
"k_1g1o0d0": "[彩球]@2x.png",
"k_1g6a6yq": "[菜刀]@2x.png",
"k_1g6vqo2": "[差劲]@2x.png",
"k_1g0kvjc": "[钞票]@2x.png",
"k_1g65x7e": "[车厢]@2x.png",
"k_0e1tjol": "[打哈欠]@2x.png",
"k_1g65n58": "[大兵]@2x.png",
"k_1g7se7o": "[大哭]@2x.png",
"k_1g03868": "[蛋糕]@2x.png",
"k_1h8nm66": "[刀]@2x.png",
"k_1g3dlpi": "[得意]@2x.png",
"k_1g3u434": "[灯泡]@2x.png",
"k_1giuqs7": "[凋谢]@2x.png",
"k_1g8r0r9": "[多云]@2x.png",
"k_1g7k6i1": "[发呆]@2x.png",
"k_1g44zsp": "[发抖]@2x.png",
"k_1g5l96i": "[飞机]@2x.png",
"k_1g7wsqj": "[飞吻]@2x.png",
"k_1g49luq": "[奋斗]@2x.png",
"k_1gixbsm": "[风车]@2x.png",
"k_1g6cqbq": "[尴尬]@2x.png",
"k_1g6jbw5": "[勾引]@2x.png",
"k_1g3lwo1": "[鼓掌]@2x.png",
"k_1g13nkj": "[害羞]@2x.png",
"k_1g0mt47": "[憨笑]@2x.png",
"k_0bxujkf": "[红灯笼]@2x.png",
"k_0hhaeh8": "[红双喜]@2x.png",
"k_1g0jnts": "[坏笑]@2x.png",
"k_1g46g9c": "[挥手]@2x.png",
"k_1g4vi9g": "[回头]@2x.png",
"k_1gf7hes": "[饥饿]@2x.png",
"k_1g6mvsm": "[激动]@2x.png",
"k_1gku5mf": "[街舞]@2x.png",
"k_1g4hidg": "[惊恐]@2x.png",
"k_1gjbrtu": "[惊讶]@2x.png",
"k_1g6sand": "[咖啡]@2x.png",
"k_1g4s8rj": "[磕头]@2x.png",
"k_1g1wn34": "[可爱]@2x.png",
"k_1g3l0wd": "[可怜]@2x.png",
"k_1ggaon9": "[抠鼻]@2x.png",
"k_1ggvcb0": "[骷髅]@2x.png",
"k_1h8yqjt": "[酷]@2x.png",
"k_0jac97i": "[快哭了]@2x.png",
"k_1h8oiby": "[困]@2x.png",
"k_1g0s5hg": "[蜡烛]@2x.png",
"k_1g1iuer": "[篮球]@2x.png",
"k_1g2xjfi": "[冷汗]@2x.png",
"k_0s5oyqw": "[礼品袋]@2x.png",
"k_1g1qqvf": "[礼物]@2x.png",
"k_1g2slew": "[流汗]@2x.png",
"k_1g3z9xx": "[流泪]@2x.png",
"k_1g6pabn": "[麻将]@2x.png",
"k_0pkaxul": "[麦克风]@2x.png",
"k_1g7m0zj": "[猫咪]@2x.png",
"k_0ibvtpo": "[么么哒]@2x.png",
"k_1g1hoh1": "[玫瑰]@2x.png",
"k_1gfzeow": "[米饭]@2x.png",
"k_1g5l15p": "[面条]@2x.png",
"k_1g2hfa6": "[奶瓶]@2x.png",
"k_1gix9pj": "[难过]@2x.png",
"k_1giqn6g": "[闹钟]@2x.png",
"k_1h8kd64": "[怒]@2x.png",
"k_1g0vui9": "[怄火]@2x.png",
"k_1g1jsj7": "[皮球]@2x.png",
"k_1ghdluw": "[啤酒]@2x.png",
"k_1gl6ec7": "[瓢虫]@2x.png",
"k_1g7gg5p": "[撇嘴]@2x.png",
"k_1g8psin": "[乒乓]@2x.png",
"k_1gjzu3p": "[汽车]@2x.png",
"k_1h8mr0k": "[强]@2x.png",
"k_1g45y2n": "[敲打]@2x.png",
"k_1gkaxsl": "[青蛙]@2x.png",
"k_0jcfnoo": "[糗大了]@2x.png",
"k_1g4njy1": "[拳头]@2x.png",
"k_1h8mqr3": "[弱]@2x.png",
"k_1h926fg": "[色]@2x.png",
"k_1g6rtbq": "[沙发]@2x.png",
"k_1giirh6": "[删除]@2x.png",
"k_1g14ny9": "[闪电]@2x.png",
"k_1g6bmsr": "[胜利]@2x.png",
"k_1g1rytx": "[示爱]@2x.png",
"k_1g52fbz": "[手枪]@2x.png",
"k_1h90dam": "[衰]@2x.png",
"k_1gigiae": "[睡觉]@2x.png",
"k_1gijchz": "[太阳]@2x.png",
"k_1g1sgji": "[跳绳]@2x.png",
"k_1gjwuri": "[跳跳]@2x.png",
"k_1g0juhk": "[偷笑]@2x.png",
"k_1h8nzla": "[吐]@2x.png",
"k_1g6cv0i": "[委屈]@2x.png",
"k_1g46l5g": "[握手]@2x.png",
"k_1g2pgkd": "[西瓜]@2x.png",
"k_1ging9p": "[下雨]@2x.png",
"k_1h8nzil": "[吓]@2x.png",
"k_1g7q7wr": "[献吻]@2x.png",
"k_1gl6uum": "[香蕉]@2x.png",
"k_1g23fys": "[象棋]@2x.png",
"k_0j75rdh": "[心碎了]@2x.png",
"k_1g6ajj2": "[信封]@2x.png",
"k_1g21prz": "[熊猫]@2x.png",
"k_1h8octi": "[嘘]@2x.png",
"k_1h91zox": "[药]@2x.png",
"k_1ghttfl": "[疑问]@2x.png",
"k_1ghk7sz": "[阴险]@2x.png",
"k_0gl37zz": "[右车头]@2x.png",
"k_0ifkj1p": "[右哼哼]@2x.png",
"k_0g1yh2e": "[右太极]@2x.png",
"k_1g9dkfc": "[雨伞]@2x.png",
"k_1g8jl88": "[月亮]@2x.png",
"k_1h8lhqj": "[晕]@2x.png",
"k_1gi9x2q": "[再见]@2x.png",
"k_1g6dwwv": "[炸弹]@2x.png",
"k_1fzmkfi": "[折磨]@2x.png",
"k_1g6jbiw": "[纸巾]@2x.png",
"k_1ggjnwu": "[咒骂]@2x.png",
"k_1g4qlq8": "[猪头]@2x.png",
"k_1g1lqzz": "[抓狂]@2x.png",
"k_1g80j3u": "[转圈]@2x.png",
"k_1g0z55s": "[龇牙]@2x.png",
"k_1g3ju6v": "[钻戒]@2x.png",
"k_0gl51l6": "[左车头]@2x.png",
"k_0iflllk": "[左哼哼]@2x.png",
"k_0g1y3ir": "[左太极]@2x.png",
"k_026hiq5": "消息列表加载中",
"k_003tu8k": "爱你",
"k_003myvp": "傲慢",
"k_003kddw": "白眼",
"k_039yfhv": "棒棒糖",
"k_003nu3p": "抱抱",
"k_003nijr": "抱拳",
"k_003mg88": "爆筋",
"k_002v17e": "鄙视",
"k_003qhy4": "闭嘴",
"k_003l5fq": "鞭炮",
"k_003uacl": "便便",
"k_003oq1g": "擦汗",
"k_003qvey": "彩带",
"k_003jci7": "彩球",
"k_003pyu1": "菜刀",
"k_003q97d": "差劲",
"k_003po5d": "车厢",
"k_03eadb2": "打哈欠",
"k_003pnuf": "大兵",
"k_003kg57": "蛋糕",
"k_003mxkt": "得意",
"k_003onu3": "灯泡",
"k_002uv8s": "凋谢",
"k_003kqy0": "调皮",
"k_003tyum": "多云",
"k_003pv9u": "发呆",
"k_036o6mu": "发抖t",
"k_003nogx": "飞机",
"k_003q7wg": "飞吻",
"k_003m0jd": "奋斗",
"k_002ult9": "风车",
"k_003r8gt": "尴尬",
"k_003qy4u": "勾引",
"k_003mnoa": "鼓掌",
"k_003lmw8": "害羞",
"k_003mb30": "憨笑",
"k_03bj41g": "红灯笼",
"k_03dxw2f": "红双喜",
"k_003mk57": "坏笑",
"k_003nmvf": "挥手",
"k_003r2i7": "回头",
"k_002s6f3": "饥饿",
"k_003qd0t": "激动",
"k_002vgi4": "街舞",
"k_003nz33": "惊恐",
"k_002wh4p": "惊讶",
"k_003ozpu": "咖啡",
"k_003qvs4": "磕头",
"k_003l3wb": "可爱",
"k_003nuwm": "可怜",
"k_002rw1q": "抠鼻",
"k_002tujb": "骷髅",
"k_00030eq": "酷",
"k_03i8ath": "快哭了",
"k_000421h": "困",
"k_003l5i7": "蜡烛",
"k_003j72g": "篮球",
"k_003ofwl": "冷汗",
"k_02mw65v": "礼品袋",
"k_003ku40": "礼物",
"k_003ookz": "流汗",
"k_003on72": "流泪",
"k_003rjy0": "麻将",
"k_003q2f8": "猫咪",
"k_03et393": "么么哒",
"k_003j7j2": "玫瑰",
"k_002sr0b": "米饭",
"k_003nnza": "面条",
"k_003jef9": "奶瓶",
"k_002umn0": "难过",
"k_002rjib": "闹钟",
"k_0003zcn": "怒",
"k_003jzwq": "怄火",
"k_003j4js": "皮球",
"k_002r5ir": "啤酒",
"k_002ubu4": "瓢虫",
"k_003ppo6": "撇嘴",
"k_003ty3o": "乒乓",
"k_002vxwe": "汽车",
"k_00043hb": "强",
"k_003nmbo": "敲打",
"k_002tfhq": "青蛙",
"k_03i7lrn": "糗大了",
"k_003r03m": "拳头",
"k_00043h0": "弱",
"k_000345z": "色",
"k_003qmp9": "沙发",
"k_003it8a": "闪电",
"k_003pxow": "胜利",
"k_003kw8e": "示爱",
"k_003n99g": "手枪",
"k_00035cl": "衰",
"k_002vl3h": "睡觉",
"k_002rgqk": "太阳",
"k_003m9d1": "跳绳",
"k_002vobp": "跳跳",
"k_003mkoz": "偷笑",
"k_00041px": "吐",
"k_003rjh5": "委屈",
"k_003j36u": "西瓜",
"k_002re92": "下雨",
"k_00041py": "吓",
"k_003q06o": "献吻",
"k_002ubjp": "香蕉",
"k_003o2tr": "象棋",
"k_03ie6pa": "心碎了",
"k_003rao5": "信封",
"k_003l3us": "熊猫",
"k_000424d": "嘘",
"k_00033yi": "药",
"k_002qtyy": "疑问",
"k_002qe0o": "阴险",
"k_03gu7us": "右车头",
"k_03ere8m": "右哼哼",
"k_003uqk3": "雨伞",
"k_003tzdv": "月亮",
"k_0003z00": "晕",
"k_002vdrd": "再见",
"k_003ra1w": "炸弹",
"k_003lcad": "折磨",
"k_003q7sz": "纸巾",
"k_002thn9": "咒骂",
"k_003qx7f": "猪头",
"k_003l044": "抓狂",
"k_003qg4h": "转圈",
"k_003kb97": "龇牙",
"k_03gu53l": "左车头",
"k_03erd1f": "左哼哼",
"k_003nyvl": "爱情",
"k_003r85z": "爱心",
"k_003mk8j": "钞票",
"k_003pwfj": "大哭",
"k_00042w5": "刀",
"k_003nmtr": "握手",
"k_03c529p": "右太极",
"k_003n4mk": "钻戒",
"k_03c5488": "左太极",
"k_1llp7tu": "该用户不存在",
"k_0tbyqyb": "加载中…",
"k_0td1p3f": "保存中…",
"k_1klqdh1": "仅限汉字、英文、数字和下划线",
"k_03el5lp": "未填写",
"k_1ui0gai": "搜索指定内容",
"k_003nvk2": "消息",
"k_03agld7": "群提示",
"k_0elt0kw": "添加群聊",
"k_0s3sgel": "移出黑名单",
"k_1qqgjra": "$option3条未读消息",
"k_0uubyjr": "以下为未读消息",
"k_16as7eq": "表情回应",
"k_003s12u": "回复",
"k_003s38r": "更多",
"k_002wkr3": "翻译",
"k_13g4hxv": "翻译完成",
"k_003molk": "表情",
"k_165bbw6": "消息历史",
"k_13sqc0z": "清除消息",
"k_0glns86": "删除会话",
"k_13s99rx": "清空消息",
"k_11vsa3j": "退出群组",
"k_11vvszp": "解散群组",
"k_15i9w72": "群管理员",
"k_0p3espj": "设置备注名",
"k_118sw9v": "立即搜索",
"k_0h20hg5": "视频通话",
"k_0h22snw": "语音通话",
"k_003lz6t": "对方",
"k_1xf4yre": "发送给$option1",
"k_003por5": "截图",
"k_1rw7s82": " 访问相册中视频权限,以正常使用发送视频等功能。",
"k_003rcwm": "打开",
"k_1698c42": "在访达中打开",
"k_066fxsz": "查看文件夹",
"k_0k432y2": "无法发送,包含文件夹",
"k_002wb4y": "会话",
"k_0od4qyh": "视频文件异常",
"k_1bfkxg9": "不支持 0KB 文件的传输",
"k_0vvsw7g": "文件处理异常",
"k_06e224q": "[消息被管理员撤回]",
"k_1u1mjcl": "[消息被撤回]",
"k_1qcqxea": "选择多个会话",
"k_1qgmc20": "选择一个会话",
"k_1d8nx6f": "在新窗口中打开",
"k_1hz05ax": "正在下载原始资源,请稍候...",
"k_002robo": "清除"
}
+4
View File
@@ -0,0 +1,4 @@
import 'package:tencent_cloud_chat_uikit/import_proxy/import_proxy.dart';
ImportProxy getImportProxy() =>
throw UnsupportedError('Cannot create a ImportProxy.');
+11
View File
@@ -0,0 +1,11 @@
import 'package:tencent_cloud_chat_uikit/import_proxy/general.dart'
// ignore: uri_does_not_exist
if (dart.library.html) 'platform/web_import.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'platform/native_import.dart';
abstract class ImportProxy {
getFlutterPluginRecord();
factory ImportProxy() => getImportProxy();
}
@@ -0,0 +1,11 @@
import 'package:flutter_plugin_record_plus/flutter_plugin_record.dart';
import 'package:tencent_cloud_chat_uikit/import_proxy/import_proxy.dart';
class NativeImport implements ImportProxy {
@override
FlutterPluginRecord? getFlutterPluginRecord() {
return FlutterPluginRecord();
}
}
ImportProxy getImportProxy() => NativeImport();
+10
View File
@@ -0,0 +1,10 @@
import 'package:tencent_cloud_chat_uikit/import_proxy/import_proxy.dart';
class WebImport implements ImportProxy {
@override
void getFlutterPluginRecord() {
return;
}
}
ImportProxy getImportProxy() => WebImport();
+63
View File
@@ -0,0 +1,63 @@
library tencent_cloud_chat_uikit;
import 'package:tencent_cloud_chat_sdk/manager/v2_tim_manager.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'data_services/core/core_services_implements.dart';
export 'data_services/core/core_services_implements.dart';
// Sticker
export 'package:tim_ui_kit_sticker_plugin/tim_ui_kit_sticker_plugin.dart';
// Widgets
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_uikit_conversation.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_chat.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/tim_uikit_profile.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_operation_item.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitContact/tim_uikit_contact.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroup/tim_uikit_group.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitBlackList/tim_uikit_black_list.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitNewContact/tim_uikit_new_contact.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitNewContact/tim_uikit_unread_count.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroupProfile/tim_uikit_group_profile.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart';
export 'package:tencent_cloud_chat_uikit/ui/widgets/unread_message.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitAddFriend/tim_uikit_add_friend.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitAddGroup/tim_uikit_add_group.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitSearch/tim_uikit_search_msg_detail.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitGroup/tim_uikit_group_application_list.dart';
export 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/models/link_preview_content.dart';
export 'package:tencent_cloud_chat_uikit/ui/widgets/column_menu.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitProfile/widget/tim_uikit_profile_userinfo_card/tim_uikit_profile_userinfo_card.dart';
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitConversation/tim_ui_kit_conversation_total_unread.dart';
// Enum
export 'package:tencent_cloud_chat_uikit/ui/theme/tim_uikit_message_theme.dart';
// Controller
export 'package:tencent_cloud_chat_uikit/ui/controller/tim_uikit_profile_controller.dart';
// Config
export 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart';
export 'package:permission_handler/permission_handler.dart';
// Utils
export 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
export 'package:tencent_cloud_uikit_core/tencent_cloud_uikit_core.dart';
class TIMUIKitCore {
static CoreServicesImpl getInstance() {
setupServiceLocator();
return serviceLocator<CoreServicesImpl>();
}
static V2TIMManager getSDKInstance() {
return TencentImSDKPlugin.v2TIMManager;
}
}
+37
View File
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
Color hexToColor(String hexString) {
return Color(int.parse(hexString, radix: 16)).withAlpha(255);
}
class CommonColor {
static const weakBackgroundColor = Color(0xFFEDEDED);
static const weakDividerColor = Color(0xFFE5E6E9);
static const primaryColor = Color(0xFF147AFF);
static const lightPrimaryColor = Color(0xFFC0E1FF);
static const secondaryColor = Color(0xFF147AFF);
static const weakTextColor = Color(0xFF999999);
static const infoColor = Color(0xFFFF9C19);
static const cautionColor = Color(0xFFFF584C);
static const ownerColor = Colors.orange;
static const adminColor = Colors.blue;
static const inputFillColor = Color.fromARGB(0, 242, 243, 245);
static const textgrey = Color(0xFFAEA4A3);
static const defaultTheme = TUITheme(
weakBackgroundColor: Color(0xFFEDEDED),
weakDividerColor: Color(0xFFE5E6E9),
primaryColor: Color(0xFF147AFF),
secondaryColor: Color(0xFF147AFF),
infoColor: Color(0xFFFF9C19),
lightPrimaryColor: Color(0xFFC0E1FF),
weakTextColor: Color(0xFF999999),
darkTextColor: Color(0xFF444444),
cautionColor: Color(0xFFFF584C),
ownerColor: Colors.orange,
adminColor: Colors.blue,
inputFillColor: Color.fromARGB(0, 242, 243, 245),
textgrey: Color(0xFFAEA4A3),
);
}
+597
View File
@@ -0,0 +1,597 @@
import 'package:flutter/material.dart';
class TUITheme {
const TUITheme({
this.primaryColor = const Color(0xFF00449E),
this.secondaryColor = const Color(0xFF147AFF),
this.infoColor = const Color(0xFFFF9C19),
this.weakBackgroundColor = const Color(0xFFEDEDED),
this.wideBackgroundColor = Colors.white,
this.weakDividerColor = const Color(0xFFE5E6E9),
this.weakTextColor = const Color(0xFF999999),
this.darkTextColor = const Color(0xFF444444),
this.lightPrimaryColor = const Color(0xFF3371CD),
this.textColor,
this.cautionColor = const Color(0xFFFF584C),
this.ownerColor = Colors.orange,
this.adminColor = Colors.blue,
this.white = Colors.white,
this.black = Colors.black,
this.inputFillColor = const Color(0xFFEDEDED),
this.textgrey = const Color(0xFFAEA4A3),
/// 消息列表多选面板背景颜色
this.selectPanelBgColor = const Color(0xFFF9F9FA),
/// 消息列表多选面板文字及icon颜色
this.selectPanelTextIconColor = const Color(0xFF37393F),
/// Appbar 背景颜色
this.appbarBgColor = const Color(0xFFF2F3F5),
/// Appbar 文字颜色
this.appbarTextColor = const Color(0xFF010000),
/// 会话列表背景颜色
this.conversationItemBgColor = Colors.white, // 1
/// 会话列表边框颜色
this.conversationItemBorderColor = const Color(0xFFE5E6E9), // 1
/// 会话列表选中背景颜色
this.conversationItemActiveBgColor = const Color(0xFFEDEDED), // 1
/// 会话列表置顶背景颜色
this.conversationItemPinedBgColor = const Color(0xFFEDEDED), // 1
/// 会话列表Title字体颜色
this.conversationItemTitleTextColor = Colors.black, // 1
/// 会话列表LastMessage字体颜色
this.conversationItemLastMessageTextColor = const Color(0xFF999999), // 1
/// 会话列表Time字体颜色
this.conversationItemTitmeTextColor = const Color(0xFF999999), // 1
/// 会话列表用户在线状态背景色
this.conversationItemOnlineStatusBgColor = Colors.green, // 1
/// 会话列表用户离线状态背景色
this.conversationItemOfflineStatusBgColor = Colors.grey, // 1
/// 会话列表未读数背景颜色
this.conversationItemUnreadCountBgColor = const Color(0xFFFF584C), // 1
/// 会话列表未读数字体颜色
this.conversationItemUnreadCountTextColor = Colors.white, // 1
/// 会话列表草稿字体颜色
this.conversationItemDraftTextColor = const Color(0xFFFF584C), // 1
/// 会话列表收到消息不提醒Icon颜色
this.conversationItemNoNotificationIconColor = const Color(0xFF999999), // 1
/// 会话列表侧滑按钮字体颜色
this.conversationItemSliderTextColor = Colors.white, // 1
/// 会话列表侧滑按钮Clear背景颜色
this.conversationItemSliderClearBgColor = const Color(0xFF00449E), // 1
/// 会话列表侧滑按钮Pin背景颜色
this.conversationItemSliderPinBgColor = const Color(0xFFFF9C19), // 1
/// 会话列表侧滑按钮Delete背景颜色
this.conversationItemSliderDeleteBgColor = Colors.red, // 1
/// 会话列表宽屏模式选中时背景颜色
this.conversationItemChooseBgColor = const Color(0xFFE7F0FF), // 1
/// 聊天页背景颜色
this.chatBgColor, // 1
/// 桌面端消息输入框背景颜色
this.desktopChatMessageInputBgColor, // 1
/// 聊天页背景颜色
this.chatTimeDividerTextColor = const Color(0xFF999999), // 1
/// 聊天页导航栏背景颜色
this.chatHeaderBgColor = const Color(0xFFF2F3F5),
/// 聊天页导航栏Title字体颜色
this.chatHeaderTitleTextColor = const Color(0xFF010000),
/// 聊天页导航栏Back字体颜色
this.chatHeaderBackTextColor = const Color(0xFF010000),
/// 聊天页导航栏Action字体颜色
this.chatHeaderActionTextColor = const Color(0xFF010000),
/// 聊天页历史消息列表字体颜色
this.chatMessageItemTextColor = Colors.black, // 1
/// 聊天页历史消息列表来自自己时背景颜色
this.chatMessageItemFromSelfBgColor = const Color(0xFFD1E3FF),
/// 聊天页历史消息列表来自非自己时背景颜色
this.chatMessageItemFromOthersBgColor = const Color(0xFFEDEDED), // 1
/// 聊天页历史消息列表已读状态字体颜色
this.chatMessageItemUnreadStatusTextColor = const Color(0xFF999999), // 1
/// 聊天页历史消息列表小舌头背景颜色
this.chatMessageTongueBgColor = const Color(0xFFAEA4A3),
/// 聊天页历史消息列表小舌头字体颜色
this.chatMessageTongueTextColor = const Color(0xFFAEA4A3),
});
// 应用主色
// Primary Color For The App
final Color? primaryColor;
// 应用次色
// Secondary Color For The App
final Color? secondaryColor;
// 提示颜色,用于次级操作或提示
// Info Color, Used For Secondary Action Or Info
final Color? infoColor;
// 浅背景颜色,比主背景颜色浅,用于填充缝隙或阴影
// Weak Background Color, Lighter Than Main Background, Used For Marginal Space Or Shadowy Space
final Color? weakBackgroundColor;
// 宽屏幕:浅白背景颜色,比浅背景颜色浅
// Weak Background Color, Lighter Than Main Background, Used For Marginal Space Or Shadowy Space
final Color? wideBackgroundColor;
// 浅分割线颜色,用于分割线或边框
// Weak Divider Color, Used For Divider Or Border
final Color? weakDividerColor;
// 浅字色
// Weak Text Color
final Color? weakTextColor;
// 深字色
// Dark Text Color
final Color? darkTextColor;
// 浅主色,用于AppBar或Panels
// Light Primary Color, Used For AppBar Or Several Panels
final Color? lightPrimaryColor;
// 字色
// TextColor
final Color? textColor;
// 警示色,用于危险操作
// Caution Color, Used For Warning Actions
final Color? cautionColor;
// 群主标识色
// Group Owner Identification Color
final Color? ownerColor;
// 群管理员标识色
// Group Admin Identification Color
final Color? adminColor;
// 白色
// white
final Color? white;
// 黑色
// black
final Color? black;
// 输入框填充色
// input fill color
final Color? inputFillColor;
// 灰色文本
// grey text color
final Color? textgrey;
/// 新版本颜色从这里开始
///
/// Appbar 背景颜色
final Color? appbarBgColor;
/// Appbar 文字颜色
final Color? appbarTextColor;
/// 消息列表多选面板背景颜色
final Color? selectPanelBgColor;
/// 消息列表多选面板文字及icon颜色
final Color? selectPanelTextIconColor;
/// 会话列表背景颜色
final Color? conversationItemBgColor;
/// 会话列表边框颜色
final Color? conversationItemBorderColor;
/// 会话列表选中背景颜色
final Color? conversationItemActiveBgColor;
/// 会话列表置顶背景颜色
final Color? conversationItemPinedBgColor;
/// 会话列表Title字体颜色
final Color? conversationItemTitleTextColor;
/// 会话列表LastMessage字体颜色
final Color? conversationItemLastMessageTextColor;
/// 会话列表Time字体颜色
final Color? conversationItemTitmeTextColor;
/// 会话列表用户在线状态背景色
final Color? conversationItemOnlineStatusBgColor;
/// 会话列表用户离线状态背景色
final Color? conversationItemOfflineStatusBgColor;
/// 会话列表未读数背景颜色
final Color? conversationItemUnreadCountBgColor;
/// 会话列表未读数字体颜色
final Color? conversationItemUnreadCountTextColor;
/// 会话列表草稿字体颜色
final Color? conversationItemDraftTextColor;
/// 会话列表收到消息不提醒Icon颜色
final Color? conversationItemNoNotificationIconColor;
/// 会话列表侧滑按钮字体颜色
final Color? conversationItemSliderTextColor;
/// 会话列表侧滑按钮Clear背景颜色
final Color? conversationItemSliderClearBgColor;
/// 会话列表侧滑按钮Pin背景颜色
final Color? conversationItemSliderPinBgColor;
/// 会话列表侧滑按钮Delete背景颜色
final Color? conversationItemSliderDeleteBgColor;
/// 会话列表宽屏模式选中时背景颜色
final Color? conversationItemChooseBgColor;
/// 聊天页背景颜色
final Color? chatBgColor;
/// 桌面端消息输入框背景颜色
final Color? desktopChatMessageInputBgColor;
/// 聊天页背景颜色
final Color? chatTimeDividerTextColor;
/// 聊天页导航栏背景颜色
final Color? chatHeaderBgColor;
/// 聊天页导航栏Title字体颜色
final Color? chatHeaderTitleTextColor;
/// 聊天页导航栏Back字体颜色
final Color? chatHeaderBackTextColor;
/// 聊天页导航栏Action字体颜色
final Color? chatHeaderActionTextColor;
/// 聊天页历史消息列表字体颜色
final Color? chatMessageItemTextColor;
/// 聊天页历史消息列表来自自己时背景颜色
final Color? chatMessageItemFromSelfBgColor;
/// 聊天页历史消息列表来自非自己时背景颜色
final Color? chatMessageItemFromOthersBgColor;
/// 聊天页历史消息列表已读状态字体颜色
final Color? chatMessageItemUnreadStatusTextColor;
/// 聊天页历史消息列表小舌头背景颜色
final Color? chatMessageTongueBgColor;
/// 聊天页历史消息列表小舌头字体颜色
final Color? chatMessageTongueTextColor;
static const TUITheme light = TUITheme();
static const TUITheme dark = TUITheme();
MaterialColor get primaryMaterialColor => createMaterialColor(primaryColor!);
MaterialColor get lightPrimaryMaterialColor =>
createMaterialColor(lightPrimaryColor!);
TUITheme.fromJson(Map<String, dynamic> json)
: primaryColor = json['primaryColor'] as Color?,
secondaryColor = json['secondaryColor'] as Color?,
infoColor = json['infoColor'] as Color?,
weakBackgroundColor = json['weakBackgroundColor'] as Color?,
wideBackgroundColor = json['wideBackgroundColor'] as Color?,
weakDividerColor = json['weakDividerColor'] as Color?,
weakTextColor = json['weakTextColor'] as Color?,
darkTextColor = json['darkTextColor'] as Color?,
lightPrimaryColor = json['lightPrimaryColor'] as Color?,
textColor = json['textColor'] as Color?,
cautionColor = json['cautionColor'] as Color?,
ownerColor = json['ownerColor'] as Color?,
white = json['white'] as Color?,
black = json['black'] as Color?,
inputFillColor = json["inputFillColor"] as Color?,
textgrey = json['textgrey'] as Color?,
adminColor = json['adminColor'] as Color?,
selectPanelBgColor = json['selectPanelBgColor'] as Color?,
selectPanelTextIconColor = json['selectPanelTextIconColor'] as Color?,
appbarBgColor = json['appbarBgColor'] as Color?,
appbarTextColor = json['appbarTextColor'] as Color?,
/// 会话列表背景颜色
conversationItemBgColor = json['conversationItemBgColor'] as Color?,
/// 会话列表边框颜色
conversationItemBorderColor =
json['conversationItemBorderColor'] as Color?,
/// 会话列表选中背景颜色
conversationItemActiveBgColor =
json['conversationItemActiveBgColor'] as Color?,
/// 会话列表置顶背景颜色
conversationItemPinedBgColor =
json['conversationItemPinedBgColor'] as Color?,
/// 会话列表Title字体颜色
conversationItemTitleTextColor =
json['conversationItemTitleTextColor'] as Color?,
/// 会话列表LastMessage字体颜色
conversationItemLastMessageTextColor =
json['conversationItemLastMessageTextColor'] as Color?,
/// 会话列表Time字体颜色
conversationItemTitmeTextColor =
json['conversationItemTitmeTextColor'] as Color?,
/// 会话列表用户在线状态背景色
conversationItemOnlineStatusBgColor =
json['conversationItemOnlineStatusBgColor'] as Color?,
/// 会话列表用户离线状态背景色
conversationItemOfflineStatusBgColor =
json['conversationItemOfflineStatusBgColor'] as Color?,
/// 会话列表未读数背景颜色
conversationItemUnreadCountBgColor =
json['conversationItemUnreadCountBgColor'] as Color?,
/// 会话列表未读数字体颜色
conversationItemUnreadCountTextColor =
json['conversationItemUnreadCountTextColor'] as Color?,
conversationItemChooseBgColor =
json['conversationItemChooseBgColor'] as Color?,
/// 会话列表草稿字体颜色
conversationItemDraftTextColor =
json['conversationItemDraftTextColor'] as Color?,
/// 会话列表收到消息不提醒Icon颜色
conversationItemNoNotificationIconColor =
json['conversationItemNoNotificationIconColor'] as Color?,
/// 会话列表侧滑按钮字体颜色
conversationItemSliderTextColor =
json['conversationItemSliderTextColor'] as Color?,
/// 会话列表侧滑按钮Clear背景颜色
conversationItemSliderClearBgColor =
json['conversationItemSliderClearBgColor'] as Color?,
/// 会话列表侧滑按钮Pin背景颜色
conversationItemSliderPinBgColor =
json['conversationItemSliderPinBgColor'] as Color?,
/// 会话列表侧滑按钮Delete背景颜色
conversationItemSliderDeleteBgColor =
json['conversationItemSliderDeleteBgColor'] as Color?,
/// 聊天页背景颜色
chatBgColor = json['chatBgColor'] as Color?,
/// 桌面端消息输入框背景颜色
desktopChatMessageInputBgColor =
json['desktopChatMessageInputBgColor'] as Color?,
/// 聊天页背景颜色
chatTimeDividerTextColor = json['chatTimeDividerTextColor'] as Color?,
/// 聊天页导航栏背景颜色
chatHeaderBgColor = json['chatHeaderBgColor'] as Color?,
/// 聊天页导航栏Title字体颜色
chatHeaderTitleTextColor = json['chatHeaderTitleTextColor'] as Color?,
/// 聊天页导航栏Back字体颜色
chatHeaderBackTextColor = json['chatHeaderBackTextColor'] as Color?,
/// 聊天页导航栏Action字体颜色
chatHeaderActionTextColor = json['chatHeaderActionTextColor'] as Color?,
/// 聊天页历史消息列表字体颜色
chatMessageItemTextColor = json['chatMessageItemTextColor'] as Color?,
/// 聊天页历史消息列表来自自己时背景颜色
chatMessageItemFromSelfBgColor =
json['chatMessageItemFromSelfBgColor'] as Color?,
/// 聊天页历史消息列表来自非自己时背景颜色
chatMessageItemFromOthersBgColor =
json['chatMessageItemFromOthersBgColor'] as Color?,
/// 聊天页历史消息列表已读状态字体颜色
chatMessageItemUnreadStatusTextColor =
json['chatMessageItemUnreadStatusTextColor'] as Color?,
/// 聊天页历史消息列表小舌头背景颜色
chatMessageTongueBgColor = json['chatMessageTongueBgColor'] as Color?,
/// 聊天页历史消息列表小舌头字体颜色
chatMessageTongueTextColor =
json['chatMessageTongueTextColor'] as Color?;
toJson() => <String, dynamic>{
'primaryColor': primaryColor,
'secondaryColor': secondaryColor,
'infoColor': infoColor,
'weakBackgroundColor': weakBackgroundColor,
'wideBackgroundColor': wideBackgroundColor,
'weakDividerColor': weakDividerColor,
'weakTextColor': weakTextColor,
'darkTextColor': darkTextColor,
'lightPrimaryColor': lightPrimaryColor,
'textColor': textColor,
'cautionColor': cautionColor,
'ownerColor': ownerColor,
'adminColor': adminColor,
'white': white,
'black': black,
'inputFillColor': inputFillColor,
'textgrey': textgrey,
"selectPanelBgColor": selectPanelBgColor,
"selectPanelTextIconColor": selectPanelTextIconColor,
"appbarBgColor": appbarBgColor,
"appbarTextColor": appbarTextColor,
/// 会话列表背景颜色
"conversationItemBgColor": conversationItemBgColor,
/// 会话列表边框颜色
"conversationItemBorderColor": conversationItemBorderColor,
/// 会话列表选中背景颜色
"conversationItemActiveBgColor": conversationItemActiveBgColor,
/// 会话列表置顶背景颜色
"conversationItemPinedBgColor": conversationItemPinedBgColor,
/// 会话列表Title字体颜色
"conversationItemTitleTextColor": conversationItemTitleTextColor,
/// 会话列表LastMessage字体颜色
"conversationItemLastMessageTextColor":
conversationItemLastMessageTextColor,
/// 会话列表Time字体颜色
"conversationItemTitmeTextColor": conversationItemTitmeTextColor,
/// 会话列表用户在线状态背景色
"conversationItemOnlineStatusBgColor":
conversationItemOnlineStatusBgColor,
/// 会话列表用户离线状态背景色
"conversationItemOfflineStatusBgColor":
conversationItemOfflineStatusBgColor,
/// 会话列表未读数背景颜色
"conversationItemUnreadCountBgColor":
conversationItemUnreadCountBgColor,
/// 会话列表未读数字体颜色
"conversationItemUnreadCountTextColor":
conversationItemUnreadCountTextColor,
/// 会话列表草稿字体颜色
"conversationItemDraftTextColor": conversationItemDraftTextColor,
/// 会话列表收到消息不提醒Icon颜色
"conversationItemNoNotificationIconColor":
conversationItemNoNotificationIconColor,
/// 会话列表侧滑按钮字体颜色
"conversationItemSliderTextColor": conversationItemSliderTextColor,
/// 会话列表侧滑按钮Clear背景颜色
"conversationItemSliderClearBgColor":
conversationItemSliderClearBgColor,
/// 会话列表侧滑按钮Pin背景颜色
"conversationItemSliderPinBgColor": conversationItemSliderPinBgColor,
/// 会话列表侧滑按钮Delete背景颜色
"conversationItemSliderDeleteBgColor":
conversationItemSliderDeleteBgColor,
/// 会话列表侧滑按钮Delete背景颜色
"conversationItemChooseBgColor": conversationItemChooseBgColor,
/// 聊天页背景颜色
"chatBgColor": chatBgColor,
/// 桌面端消息输入框背景颜色
"desktopChatMessageInputBgColor": desktopChatMessageInputBgColor,
/// 聊天页背景颜色
"chatTimeDividerTextColor": chatTimeDividerTextColor,
/// 聊天页导航栏背景颜色
"chatHeaderBgColor": chatHeaderBgColor,
/// 聊天页导航栏Title字体颜色
"chatHeaderTitleTextColor": chatHeaderTitleTextColor,
/// 聊天页导航栏Back字体颜色
"chatHeaderBackTextColor": chatHeaderBackTextColor,
/// 聊天页导航栏Action字体颜色
"chatHeaderActionTextColor": chatHeaderActionTextColor,
/// 聊天页历史消息列表字体颜色
"chatMessageItemTextColor": chatMessageItemTextColor,
/// 聊天页历史消息列表来自自己时背景颜色
"chatMessageItemFromSelfBgColor": chatMessageItemFromSelfBgColor,
/// 聊天页历史消息列表来自非自己时背景颜色
"chatMessageItemFromOthersBgColor": chatMessageItemFromOthersBgColor,
/// 聊天页历史消息列表已读状态字体颜色
"chatMessageItemUnreadStatusTextColor":
chatMessageItemUnreadStatusTextColor,
/// 聊天页历史消息列表小舌头背景颜色
"chatMessageTongueBgColor": chatMessageTongueBgColor,
/// 聊天页历史消息列表小舌头字体颜色
"chatMessageTongueTextColor": chatMessageTongueTextColor,
};
MaterialColor createMaterialColor(Color color) {
List strengths = <double>[.05];
Map<int, Color> swatch = <int, Color>{};
final int r = color.red, g = color.green, b = color.blue;
for (int i = 1; i < 10; i++) {
strengths.add(0.1 * i);
}
for (var strength in strengths) {
final double ds = 0.5 - strength;
swatch[(strength * 1000).round()] = Color.fromRGBO(
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
1,
);
}
return MaterialColor(color.value, swatch);
}
}
+16
View File
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
class TUIThemeViewModel extends ChangeNotifier {
TUITheme _theme = CommonColor.defaultTheme;
TUITheme get theme {
return _theme;
}
set theme(TUITheme theme) {
_theme = theme;
notifyListeners();
}
}
BIN
View File
Binary file not shown.
@@ -0,0 +1,32 @@
enum V2TimImageTypesEnum {
original,
big,
small,
}
class HistoryMessageDartConstant {
static const getCount = 20;
// ignore: constant_identifier_names
static const V2_TIM_IMAGE_TYPES = {
'ORIGINAL': 0,
'BIG': 1,
'SMALL': 2,
};
static Map<V2TimImageTypesEnum, List<String>> imgPriorMap = {
V2TimImageTypesEnum.original: oriImgPrior,
V2TimImageTypesEnum.big: bigImgPrior,
V2TimImageTypesEnum.small: smallImgPrior,
};
// 缩略图优先,大图次之,最后是原图
static const smallImgPrior = ['ORIGINAL', 'BIG', 'SMALL'];
// 大图优先,原图次之,最后是缩略图
static const bigImgPrior = ['SMALL', 'ORIGINAL', 'BIG'];
// 原图优先,大图次之,最后是缩略图
static const oriImgPrior = ['SMALL', 'BIG', 'ORIGINAL'];
// 视频、音频已读状态
static const int read = 1;
}
+9
View File
@@ -0,0 +1,9 @@
// ignore_for_file: constant_identifier_names
class TimeConst {
static const DAY_SEC = 86400;
static const HOUR_SEC = 3600;
static const MIN_SEC = 60;
static const SEC_SERIES = [HOUR_SEC, MIN_SEC];
}
@@ -0,0 +1,378 @@
// ignore_for_file: avoid_print, empty_catches
import 'package:flutter/cupertino.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_cloud_chat_sdk/enum/history_msg_get_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_priority_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/offlinePushInfo.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
class TIMUIKitChatController {
late TUIChatSeparateViewModel? model;
late TIMUIKitInputTextFieldController? textFieldController;
late AutoScrollController? scrollController;
final TUIChatGlobalModel globalChatModel = serviceLocator<TUIChatGlobalModel>();
TIMUIKitChatController({TUIChatSeparateViewModel? viewModel}) {
if (viewModel != null) {
model = viewModel;
}
}
Future<bool> loadHistoryMessageList(
{HistoryMsgGetTypeEnum getType = HistoryMsgGetTypeEnum.V2TIM_GET_CLOUD_OLDER_MSG,
String? userID,
String? groupID,
int lastMsgSeq = -1,
required int count,
String? lastMsgID,
LoadDirection direction = LoadDirection.previous}) async {
return await model?.loadChatRecord(
count: count,
getType: getType,
lastMsgID: lastMsgID,
lastMsgSeq: lastMsgSeq,
) ??
false;
}
/// Clear the current conversation;
@Deprecated("No need to dispose after tencent_cloud_chat_uikit 0.1.4")
dispose() {}
/// Clear the history of current conversation;
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
clearHistory([String? convID]) {
if (convID != null) {
return globalChatModel.setMessageList(convID, []);
}
return model?.clearHistory();
}
/// Refresh the history message list manually;
Future<bool> refreshCurrentHistoryList([String? convID, ConvType? convType]) async {
if (model != null) {
try {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
} catch (e) {}
return model!.loadDataFromController();
} else {
return false;
}
}
/// Update single message at UI model
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<void> updateMessage(
{
/// The ID of the target conversation
String? convID,
/// The type of the target conversation
ConvType? convType,
/// message ID
required String msgID}) async {
if (convID != null && convType != null) {
return globalChatModel.updateMessageFromController(
msgID: msgID, conversationID: convID, conversationType: convType);
} else if (model != null) {
return model!.updateMessageFromController(msgID: msgID);
}
return;
}
/// Sends a message to the specified conversation, or to the current conversation specified on `TIMUIKitChat`.
/// You must provide `convType` and either `userID` or `groupID`, only if you use `TIMUIKitChat` without specifying a `TIMUIKitChatController`, you must provide these parameters.
Future<V2TimValueCallback<V2TimMessage>?>? sendMessage({
required V2TimMessage? messageInfo,
/// The type of the target conversation: either ConvType.group or ConvType.c2c. Required if using `TIMUIKitChat` without specifying a `TIMUIKitChatController`.
ConvType? convType,
/// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c.
String? userID,
/// The target group ID. Required if convType is ConvType.group.
String? groupID,
/// A callback function to update the input field when message sending fails.
ValueChanged<String>? setInputField,
/// Offline push information.
OfflinePushInfo? offlinePushInfo,
/// Whether automatically scrolling to the bottom of the message list after sending a message.
/// This field solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
bool isNavigateToMessageListBottom = true,
/// Message priorities. This field is valid only for group chat messages.
/// You are advised to set higher priorities for important messages (such as red packet and gift messages)
/// and set lower priorities for frequent but unimportant messages (such as like messages).
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
/// Whether the message can be received only by online users.
/// If this field is set to true, the message cannot be pulled in recipient historical message pulling.
/// This field is often used to implement weak notification features such as "The other party is typing" or unimportant notifications in the group. This field is not supported by audio-video groups (AVChatRoom).
bool? onlineUserOnly,
/// Whether the message is excluded from the conversation unread message count.
bool? isExcludedFromUnreadCount,
/// Whether a read receipt is required.
bool? needReadReceipt,
/// Cloud custom data (saved in the cloud, will be sent to the peer end,
/// and can still be pulled after the app is uninstalled and reinstalled)
String? cloudCustomData,
/// Local custom message data (saved locally, will not be sent to the peer end,
/// and will become invalid after the app is uninstalled and reinstalled).
String? localCustomData,
}) {
if (convType != null) {
/// Sends a message to the specified conversation.
assert((groupID == null) != (userID == null));
assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom && scrollController != null) {
try {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
} catch (e) {}
}
return globalChatModel.sendMessageFromController(
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
cloudCustomData: cloudCustomData,
localCustomData: localCustomData,
messageInfo: messageInfo,
convType: convType,
convID: (convType == ConvType.group ? groupID : userID) ?? "",
setInputField: setInputField,
offlinePushInfo: offlinePushInfo);
} else if (model != null) {
/// Sends a message to the current conversation specified on `TIMUIKitChat`. 发送到 `TIMUIKitChat` 中指定的当前对话。
if (isNavigateToMessageListBottom && scrollController != null) {
try {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
} catch (e) {}
}
return model!.sendMessageFromController(
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
cloudCustomData: cloudCustomData,
localCustomData: localCustomData,
messageInfo: messageInfo,
offlinePushInfo: offlinePushInfo);
}
return null;
}
/// Sends a message, replying to another message, to the specified conversation, or to the current conversation specified on `TIMUIKitChat`.
/// You must provide `convType` and either `userID` or `groupID`, only if you use `TIMUIKitChat` without specifying a `TIMUIKitChatController`, you must provide these parameters.
Future<V2TimValueCallback<V2TimMessage>?>? sendReplyMessage({
required String messageText,
required V2TimMessage messageBeenReplied,
/// The type of the target conversation: either ConvType.group or ConvType.c2c. Required if using `TIMUIKitChat` without specifying a `TIMUIKitChatController`.
ConvType? convType,
/// The user ID of the target one-to-one conversation. Required if convType is ConvType.c2c.
String? userID,
/// The target group ID. Required if convType is ConvType.group.
String? groupID,
/// A callback function to update the input field when message sending fails.
ValueChanged<String>? setInputField,
/// Offline push information.
OfflinePushInfo? offlinePushInfo,
/// Whether automatically scrolling to the bottom of the message list after sending a message.
/// This field solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
bool isNavigateToMessageListBottom = true,
/// Message priorities. This field is valid only for group chat messages.
/// You are advised to set higher priorities for important messages (such as red packet and gift messages)
/// and set lower priorities for frequent but unimportant messages (such as like messages).
MessagePriorityEnum priority = MessagePriorityEnum.V2TIM_PRIORITY_NORMAL,
/// Whether the message can be received only by online users.
/// If this field is set to true, the message cannot be pulled in recipient historical message pulling.
/// This field is often used to implement weak notification features such as "The other party is typing" or unimportant notifications in the group. This field is not supported by audio-video groups (AVChatRoom).
bool? onlineUserOnly,
/// Whether the message is excluded from the conversation unread message count.
bool? isExcludedFromUnreadCount,
/// Whether a read receipt is required.
bool? needReadReceipt,
/// Local custom message data (saved locally, will not be sent to the peer end,
/// and will become invalid after the app is uninstalled and reinstalled).
String? localCustomData,
}) {
if (convType != null) {
/// Sends a message to the specified conversation.
assert((groupID == null) != (userID == null));
assert(groupID != null || convType != ConvType.group);
assert(userID != null || convType != ConvType.c2c);
if (isNavigateToMessageListBottom && scrollController != null) {
try {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
} catch (e) {}
}
return globalChatModel.sendReplyMessageFromController(
text: messageText,
messageBeenReplied: messageBeenReplied,
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
localCustomData: localCustomData,
convType: convType,
convID: (convType == ConvType.group ? groupID : userID) ?? "",
setInputField: setInputField,
offlinePushInfo: offlinePushInfo);
} else if (model != null) {
/// Sends a message to the current conversation specified on `TIMUIKitChat`. 发送到 `TIMUIKitChat` 中指定的当前对话。
if (isNavigateToMessageListBottom && scrollController != null) {
scrollController?.animateTo(
scrollController!.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
}
return globalChatModel.sendReplyMessageFromController(
text: messageText,
messageBeenReplied: messageBeenReplied,
priority: priority,
onlineUserOnly: onlineUserOnly,
isExcludedFromUnreadCount: isExcludedFromUnreadCount,
needReadReceipt: needReadReceipt,
localCustomData: localCustomData,
convType: model!.conversationType ?? ConvType.group,
convID: model!.conversationID,
setInputField: setInputField,
offlinePushInfo: offlinePushInfo);
}
return null;
}
/// Send forward message;
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
sendForwardMessage({
required List<V2TimConversation> conversationList,
}) async {
model?.sendForwardMessage(conversationList: conversationList);
}
/// Send merger message;
/// This method needs use with TIMUIKitChat directly or model been initialized.
Future<V2TimValueCallback<V2TimMessage>?> sendMergerMessage({
required List<V2TimConversation> conversationList,
required String title,
required List<String> abstractList,
required BuildContext context,
}) async {
return model?.sendMergerMessage(
conversationList: conversationList, title: title, abstractList: abstractList, context: context);
}
/// Set local custom data; returns the bool shows if succeed.
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<bool> setLocalCustomData(String msgID, String localCustomData, [String? convID]) async {
final String? conversationID = convID ?? model?.conversationID;
if (conversationID == null) {
return false;
}
return globalChatModel.setLocalCustomData(msgID, localCustomData, conversationID);
}
/// Set local custom int; returns the bool shows if succeed.
/// Please provide `convID`, if you use `TIMUIKitChatController` without specifying to a `TIMUIKitChat`.
Future<bool> setLocalCustomInt(String msgID, int localCustomInt, [String? convID]) async {
final String? conversationID = convID ?? model?.conversationID;
if (conversationID == null) {
return false;
}
return globalChatModel.setLocalCustomInt(msgID, localCustomInt, conversationID);
}
/// Get current conversation, returns UserID or GroupID if in the chat page, returns "" if not.
String getCurrentConversation() {
return globalChatModel.currentSelectedConv;
}
/// Hide all bottom panels, including the sticker panel and the additional functions panel, on mobile devices.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
void hideAllBottomPanelOnMobile() {
textFieldController?.hideAllPanel();
}
/// Mention or @ other members in a group manually.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
void mentionOtherMemberInGroup({required String showNameInMessage, required String userID}) {
textFieldController?.longPressToAt(showNameInMessage, userID);
}
/// Set the content within the message input text field.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
void setInputTextField(String text) {
textFieldController?.setTextField(text);
}
/// Returns the list of group members of current group chat based on the provided keyword.
///
/// This method filters the group members based on the given keyword. If the keyword is not provided,
/// it returns the entire list of group members. The filtering is performed by checking if the keyword
/// is contained within the userID, nickName, or friendRemark properties of each group member.
///
/// [keyword] (optional) - The keyword to filter the group members. If not provided, the entire list of group members is returned.
/// This function solely works when `TIMUIKitChatController` is specified for use within a `TIMUIKitChat`.
List<V2TimGroupMemberFullInfo> getGroupMemberList({String? keyword}) {
final List<V2TimGroupMemberFullInfo> memberList =
(model?.groupMemberList ?? []).whereType<V2TimGroupMemberFullInfo>().toList();
return TencentUtils.checkString(keyword) == null
? memberList
: memberList.where((e) {
final userID = e.userID;
final nickName = e.nickName ?? "";
final friendRemark = e.friendRemark ?? "";
return userID.contains(keyword!) || nickName.contains(keyword) || friendRemark.contains(keyword);
}).toList();
}
}
@@ -0,0 +1,75 @@
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_conversation_view_model.dart';
class TIMUIKitConversationController {
late TUIConversationViewModel model;
/// Get the selected conversation currently
V2TimConversation? get selectedConversation {
return model.selectedConversation;
}
/// Set the selected conversation currently
set selectedConversation(V2TimConversation? conversation) {
model.selectedConversation = conversation;
}
/// Get the conversation list
List<V2TimConversation?> get conversationList {
return model.conversationList;
}
/// Set the conversation list
set conversationList(List<V2TimConversation?> conversationList) {
model.conversationList = conversationList;
}
/// Load the conversation list to UI
loadData({int count = 40}) {
model.loadData(count: count);
}
/// Reload the conversation list to UI
reloadData({int count = 100}) {
model.refresh(count: count);
}
/// Pin one conversation to the top
Future<V2TimCallback> pinConversation({required String conversationID, required bool isPinned}) {
return model.pinConversation(conversationID: conversationID, isPinned: isPinned);
}
/// Set the draft for a conversation
Future<V2TimCallback> setConversationDraft({required String conversationID, String? draftText}) {
return model.setConversationDraft(conversationID: conversationID, draftText: draftText);
}
/// Clear the historical message in a specific conversation
Future<V2TimCallback?>? clearHistoryMessage({required V2TimConversation conversation}) {
final convType = conversation.type;
final convID = convType == 1 ? conversation.userID : conversation.groupID;
if (convType != null && convID != null) {
return model.clearHistoryMessage(convID: convID, convType: convType);
}
return null;
}
/// Delete a conversation
Future<V2TimCallback?> deleteConversation({required String conversationID}) {
return model.deleteConversation(conversationID: conversationID);
}
/// Clear the conversation list from UI
dispose() {
model.clearData();
}
/// Scroll to a specific conversation, this conversation must be existed in conversation list.
/// If not exist, invoking `loadData` recursively, until find the target conversation.
scrollToConversation(String conversationID) {
model.scrollToConversation = conversationID;
}
}
@@ -0,0 +1,119 @@
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_profile_view_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/text_input_bottom_sheet.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitProfileController {
late TUIProfileViewModel model;
/// Remove a user from friend or contact
Future<V2TimFriendOperationResult?> deleteFriend(String userID) {
return model.deleteFriend(userID);
}
/// Deprecated: Please use [pinConversation] instead. Pin the conversation to the top
@Deprecated("Please use [pinConversation] instead")
Future<V2TimCallback> pinedConversation(bool isPined, String convID) {
return model.pinedConversation(isPined, convID);
}
/// pin the conversation to the top
Future<V2TimCallback> pinConversation(bool isPined, String convID) {
return model.pinedConversation(isPined, convID);
}
/// add a user to block list
Future<List<V2TimFriendOperationResult>?> addUserToBlackList(bool shouldAdd, String userID) {
return model.addToBlackList(shouldAdd, userID);
}
/// Change the friend adding request verification method,
/// 0 represents "Accept all friend request",
/// 1 represents "Require approval for friend requests",
/// 2 represents "reject all friend requests".
Future<V2TimCallback> changeFriendVerificationMethod(int allowType) {
return model.changeFriendVerificationMethod(allowType);
}
/// update the remarks for other users,
Future<V2TimCallback> updateRemarks(String userID, String remark) {
return model.updateRemarks(userID, remark);
}
/// set the message from a specific user as not disturb, mute notification
Future<V2TimCallback> setMessageDisturb(String userID, bool isDisturb) {
return model.setMessageDisturb(userID, isDisturb);
}
/// Show the text input bottom sheet
showTextInputBottomSheet({
required BuildContext context,
required String title,
required String tips,
required Function(String text) onSubmitted,
required TUITheme theme,
Offset? initOffset,
String? initText,
}) {
TextInputBottomSheet.showTextInputBottomSheet(
context: context,
title: title,
tips: tips,
onSubmitted: onSubmitted,
theme: theme,
initOffset: initOffset,
initText: initText);
}
/// Load the profile data
loadData(String userID) {
model.loadData(userID: userID);
}
dispose() {
model.dispose();
}
/// Add a user as friend or contact
Future<V2TimFriendOperationResult?> addFriend(String userID) {
return model.addFriend(userID);
}
Future<V2TimCallback> updateSelfSignature(String selfSignature) {
V2TimUserFullInfo userFullInfo = V2TimUserFullInfo();
userFullInfo.selfSignature = selfSignature;
return model.updateSelfInfo(userFullInfo);
}
Future<V2TimCallback> updateNickName(String nickName) {
V2TimUserFullInfo userFullInfo = V2TimUserFullInfo();
userFullInfo.nickName = nickName;
return model.updateSelfInfo(userFullInfo);
}
/// 1:男 2:女
Future<V2TimCallback> updateGender(int gender) {
V2TimUserFullInfo userFullInfo = V2TimUserFullInfo();
userFullInfo.gender = gender;
return model.updateSelfInfo(userFullInfo);
}
Future<V2TimCallback> updateBirthday(int birthday) {
V2TimUserFullInfo userFullInfo = V2TimUserFullInfo();
userFullInfo.birthday = birthday;
return model.updateSelfInfo(userFullInfo);
}
Future<V2TimCallback> updateAvatar(String url) {
V2TimUserFullInfo userFullInfo = V2TimUserFullInfo();
userFullInfo.faceUrl = url;
return model.updateSelfInfo(userFullInfo);
}
}
+1
View File
@@ -0,0 +1 @@
+29
View File
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
class MessageThemeData {
/// Text style for message text
final TextStyle? messageTextStyle;
/// Text style for user nick name
final TextStyle? nickNameTextStyle;
/// Text style for timeline
final TextStyle? timelineTextStyle;
/// Color for messageBackgroundColor
final Color? messageBackgroundColor;
/// border radius for text message
final BorderRadius? messageBorderRadius;
final BorderRadius? avatarBorderRadius;
MessageThemeData({
this.messageTextStyle,
this.messageBackgroundColor,
this.messageBorderRadius,
this.nickNameTextStyle,
this.timelineTextStyle,
this.avatarBorderRadius,
});
}
+158
View File
@@ -0,0 +1,158 @@
class TencentUtils{
static bool isTextNotEmpty(String? text){
return text != null && text.isNotEmpty;
}
static String? checkString(String? text){
return (text != null && text.isEmpty) ? null : text;
}
static String? checkStringWithoutSpace(String? text){
if (text == null || text.trim().isEmpty || text.contains(' ')) {
return null;
}
return text;
}
static String getFileType(String fileType) {
switch (fileType) {
case "3gp":
return "video/3gpp";
case "torrent":
return "application/x-bittorrent";
case "kml":
return "application/vnd.google-earth.kml+xml";
case "gpx":
return "application/gpx+xml";
case "asf":
return "video/x-ms-asf";
case "avi":
return "video/x-msvideo";
case "bin":
case "class":
case "exe":
return "application/octet-stream";
case "bmp":
return "image/bmp";
case "c":
return "text/plain";
case "conf":
return "text/plain";
case "cpp":
return "text/plain";
case "doc":
return "application/msword";
case "docx":
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
case "xls":
case "csv":
return "application/vnd.ms-excel";
case "xlsx":
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
case "gif":
return "image/gif";
case "gtar":
return "application/x-gtar";
case "gz":
return "application/x-gzip";
case "h":
return "text/plain";
case "htm":
return "text/html";
case "html":
return "text/html";
case "jar":
return "application/java-archive";
case "java":
return "text/plain";
case "jpeg":
return "image/jpeg";
case "jpg":
return "image/jpeg";
case "js":
return "application/x-javascript";
case "log":
return "text/plain";
case "m3u":
return "audio/x-mpegurl";
case "m4a":
return "audio/mp4a-latm";
case "m4b":
return "audio/mp4a-latm";
case "m4p":
return "audio/mp4a-latm";
case "m4u":
return "video/vnd.mpegurl";
case "m4v":
return "video/x-m4v";
case "mov":
return "video/quicktime";
case "mp2":
return "audio/x-mpeg";
case "mp3":
return "audio/x-mpeg";
case "mp4":
return "video/mp4";
case "mpc":
return "application/vnd.mpohun.certificate";
case "mpe":
return "video/mpeg";
case "mpeg":
return "video/mpeg";
case "mpg":
return "video/mpeg";
case "mpg4":
return "video/mp4";
case "mpga":
return "audio/mpeg";
case "msg":
return "application/vnd.ms-outlook";
case "ogg":
return "audio/ogg";
case "pdf":
return "application/pdf";
case "png":
return "image/png";
case "pps":
return "application/vnd.ms-powerpoint";
case "ppt":
return "application/vnd.ms-powerpoint";
case "pptx":
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
case "prop":
return "text/plain";
case "rc":
return "text/plain";
case "rmvb":
return "audio/x-pn-realaudio";
case "rtf":
return "application/rtf";
case "sh":
return "text/plain";
case "tar":
return "application/x-tar";
case "tgz":
return "application/x-compressed";
case "txt":
return "text/plain";
case "wav":
return "audio/x-wav";
case "wma":
return "audio/x-ms-wma";
case "wmv":
return "audio/x-ms-wmv";
case "wps":
return "application/vnd.ms-works";
case "xml":
return "text/plain";
case "z":
return "application/x-compress";
case "zip":
return "application/x-zip-compressed";
default:
return "*/*";
}
}
}
+9
View File
@@ -0,0 +1,9 @@
class CustomSticker {
const CustomSticker(
{required this.name, required this.index, this.url, this.unicode});
final int? unicode;
final String name;
final int index;
final String? url;
}
+20
View File
@@ -0,0 +1,20 @@
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.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] ?? "";
}
}
+55
View File
@@ -0,0 +1,55 @@
// ignore_for_file: avoid_print, prefer_typing_uninitialized_variables
import 'dart:ui';
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
class Frame {
static var orginalCallback;
static init() {
orginalCallback = window.onReportTimings;
window.onReportTimings = onReportTimings;
}
// 仅缓存最近 25 帧绘制耗时
static const maxframes = 25;
static final List<FrameTiming> lastFrames = [];
// 基准 VSync 信号周期
static const frameInterval =
Duration(microseconds: Duration.microsecondsPerSecond ~/ 60);
static void onReportTimings(List<FrameTiming> timings) {
lastFrames.addAll(timings);
// 仅保留 25 帧
if (lastFrames.length > maxframes) {
lastFrames.removeRange(0, lastFrames.length - maxframes);
}
// 如果有原始帧回调函数,则执行
if (orginalCallback != null) {
orginalCallback(timings);
}
outputLogger.i("fps: $fps");
}
static double get fps {
int sum = 0;
for (FrameTiming timing in lastFrames) {
// 计算渲染耗时
int duration = timing.timestampInMicroseconds(FramePhase.rasterFinish) -
timing.timestampInMicroseconds(FramePhase.buildStart);
// 判断耗时是否在 Vsync 信号周期内
if (duration < frameInterval.inMicroseconds) {
sum += 1;
} else {
// 有丢帧,向上取整
int count = (duration / frameInterval.inMicroseconds).ceil();
sum += count;
}
}
return lastFrames.length / sum * 60;
}
static destroy() {
window.onReportTimings = null;
}
}
+13
View File
@@ -0,0 +1,13 @@
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
final outputLogger = TencentCloudChatLog();
class TencentCloudChatLog{
void i(String text){
if(!PlatformUtils().isWeb){
TencentImSDKPlugin.v2TIMManager
.uikitTrace(trace: text);
}
}
}
+346
View File
@@ -0,0 +1,346 @@
// ignore_for_file: unrelated_type_equality_checks, avoid_print
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_change_info_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_tips_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_change_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_tips_elem.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_tips_elem.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_image.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_image.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart';
import 'package:tencent_cloud_chat_uikit/ui/constants/time.dart';
import 'package:collection/collection.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class MessageUtils {
// 判断CallingData的方式和Trtc的方法一致
static isCallingData(String data) {
try {
Map<String, dynamic> customMap = jsonDecode(data);
if (customMap.containsKey('businessID') && customMap['businessID'] == 1) {
return true;
}
} catch (e) {
outputLogger.i("isCallingData json parse error");
return false;
}
return false;
}
// 是否是群组TRTC信息
static isGroupCallingMessage(V2TimMessage message) {
final isGroup = message.groupID != null;
final isCustomMessage = message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM;
if (isCustomMessage) {
final customElemData = message.customElem?.data ?? "";
return isCallingData(customElemData) && isGroup;
}
return false;
}
static String getCustomGroupCreatedOrDismissedString(V2TimMessage message) {
try {
final isGroup = message.groupID != null;
final isCustomMessage = message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM;
if (isCustomMessage && isGroup) {
final data = message.customElem?.data ?? "";
Map<String, dynamic> customMap = jsonDecode(data);
if (customMap.containsKey('businessID') && customMap['businessID'] == "group_create") {
final content = "${customMap['opUser']}${customMap['content']}";
return content;
}
return "";
}
return "";
} catch (e) {
outputLogger.i("getCustomGroupCreatedOrDismissedString json parse error");
return "";
}
}
static Future<String> _getGroupChangeType(
V2TimGroupChangeInfo info, List<V2TimGroupMemberFullInfo?> groupMemberList) async {
int? type = info.type;
var value = info.value;
String s = TIM_t('群资料信息');
switch (type) {
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_CUSTOM:
s = TIM_t("自定义字段");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_FACE_URL:
s = TIM_t("群头像");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_INTRODUCTION:
s = TIM_t("群简介");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_NAME:
s = TIM_t("群名称");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_NOTIFICATION:
s = TIM_t("群公告");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_OWNER:
s = TIM_t("群主");
final V2TimGroupMemberFullInfo? groupMemberInfo =
groupMemberList.firstWhereOrNull((element) => element?.userID == value);
if (groupMemberInfo != null) {
value = TencentUtils.checkString(groupMemberInfo.friendRemark) ??
TencentUtils.checkString(groupMemberInfo.nameCard) ??
TencentUtils.checkString(groupMemberInfo.nickName) ??
TencentUtils.checkString(groupMemberInfo.userID);
} else {
final res = await TencentImSDKPlugin.v2TIMManager.getUsersInfo(userIDList: [value ?? ""]);
if (res.code == 0) {
final List<V2TimUserFullInfo> data = res.data ?? [];
if (data.isNotEmpty) {
final firstPerson = data[0];
value = TencentUtils.checkString(firstPerson.nickName) ?? TencentUtils.checkString(firstPerson.userID);
}
}
}
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_SHUT_UP_ALL:
s = TIM_t("全员禁言状态");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_RECEIVE_MESSAGE_OPT:
s = TIM_t("消息接收方式");
break;
case GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_GROUP_ADD_OPT:
s = TIM_t("加群方式");
break;
}
final String option8 = s;
if (value != null && value.isNotEmpty) {
return TIM_t_para("{{option8}}为 ", "$option8为 ")(option8: option8) + value;
} else {
return option8;
}
}
static String? _getOpUserNick(V2TimGroupMemberInfo? opUser) {
if (opUser == null) {
return "";
}
return TencentUtils.checkString(opUser.friendRemark) ??
TencentUtils.checkString(opUser.nickName) ??
TencentUtils.checkString(opUser.userID);
}
static String? _getMemberNickName(V2TimGroupMemberInfo e) {
final friendRemark = e.friendRemark;
final nameCard = e.nameCard;
final nickName = e.nickName;
final userID = e.userID;
if (friendRemark != null && friendRemark != "") {
return friendRemark;
} else if (nameCard != null && nameCard != "") {
return nameCard;
} else if (nickName != null && nickName != "") {
return nickName;
} else {
return userID;
}
}
static Future<String> groupTipsMessageAbstract(
V2TimGroupTipsElem groupTipsElem, List<V2TimGroupMemberFullInfo?> groupMemberList) async {
String displayMessage;
final operationType = groupTipsElem.type;
final operationMember = groupTipsElem.opMember;
final memberList = groupTipsElem.memberList;
final opUserNickName = _getOpUserNick(operationMember);
switch (operationType) {
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_GROUP_INFO_CHANGE:
final String? option7 = opUserNickName ?? "";
final groupChangeInfoList = groupTipsElem.groupChangeInfoList ?? [];
String changedInfoString = "";
bool changedValue = false;
for (V2TimGroupChangeInfo? element in groupChangeInfoList) {
final newText = await _getGroupChangeType(element!, groupMemberList);
changedInfoString += (changedInfoString.isEmpty ? "" : " / ") + newText;
changedValue = element!.boolValue ?? false;
}
if (changedInfoString.isEmpty) {
changedInfoString = TIM_t("群资料");
}
if (changedInfoString == TIM_t("全员禁言状态")) {
changedInfoString = TIM_t("全员禁言");
displayMessage = changedValue == false
? TIM_t_para("{{option7}} 取消", "$option7 取消")(option7: option7) + changedInfoString
: TIM_t_para("{{option7}} 开启", "$option7 开启")(option7: option7) + changedInfoString;
} else {
displayMessage = TIM_t_para("{{option7}}修改", "$option7修改")(option7: option7) + changedInfoString;
}
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_QUIT:
final String? option6 = opUserNickName ?? "";
displayMessage = TIM_t_para("{{option6}}退出群聊", "$option6退出群聊")(option6: option6);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_INVITE:
final option5 = memberList!.map((e) => _getMemberNickName(e!).toString()).join("、");
final inviteUser = _getOpUserNick(operationMember);
displayMessage = '$inviteUser' + TIM_t_para("邀请{{option5}}加入群组", "邀请$option5加入群组")(option5: option5);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_KICKED:
final option4 = memberList!.map((e) => _getMemberNickName(e!).toString()).join("、");
final kickUser = _getOpUserNick(operationMember);
displayMessage = '$kickUser' + TIM_t_para("将{{option4}}踢出群组", "将$option4踢出群组")(option4: option4);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_JOIN:
final option3 = memberList!.map((e) => _getMemberNickName(e!).toString()).join("、");
displayMessage = TIM_t_para("用户{{option3}}加入了群聊", "用户$option3加入了群聊")(option3: option3);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_MEMBER_INFO_CHANGE:
displayMessage = groupTipsElem.memberList!.map((e) {
final changedMember =
groupTipsElem.memberChangeInfoList!.firstWhere((element) => element!.userID == e!.userID);
final isMute = changedMember!.muteTime != 0;
final option2 = _getMemberNickName(e!);
final displayMessage = isMute ? TIM_t("禁言") : TIM_t("解除禁言");
return TIM_t_para("{{option2}} 被", "$option2 被")(option2: option2) + displayMessage;
}).join("、");
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_SET_ADMIN:
final adminMember = memberList!.map((e) => _getMemberNickName(e!).toString()).join("、");
final opMember = _getOpUserNick(operationMember);
final option1 = adminMember;
displayMessage = '$opMember' + TIM_t_para("将 {{option1}} 设置为管理员", "将 $option1 设置为管理员")(option1: option1);
break;
case GroupTipsElemType.V2TIM_GROUP_TIPS_TYPE_CANCEL_ADMIN:
final adminMember = memberList!.map((e) => _getMemberNickName(e!).toString()).join("、");
final opMember = _getOpUserNick(operationMember);
final option1 = adminMember;
displayMessage = '$opMember' + TIM_t_para("将 {{option1}} 取消管理员", "将 $option1 取消管理员")(option1: option1);
break;
default:
final String option2 = operationType.toString();
displayMessage = TIM_t_para("系统消息 {{option2}}", "系统消息 $option2")(option2: option2);
break;
}
return displayMessage;
}
static String formatVideoTime(int time) {
List<int> times = [];
if (time <= 0) return '0:01';
if (time >= TimeConst.DAY_SEC) return '1d+';
for (int idx = 0; idx < TimeConst.SEC_SERIES.length; idx++) {
int sec = TimeConst.SEC_SERIES[idx];
if (time >= sec) {
times.add((time / sec).floor());
time = time % sec;
} else if (idx > 0) {
times.add(0);
}
}
times.add(time);
String formatTime = times[0].toString();
for (int idx = 1; idx < times.length; idx++) {
if (times[idx] < 10) {
formatTime += ':0${times[idx].toString()}';
} else {
formatTime += ':${times[idx].toString()}';
}
}
return formatTime;
}
static String handleCustomMessageString(V2TimMessage message) {
return TIM_t("消息");
}
static Widget wrapMessageTips(Widget child, TUITheme? theme) {
return Container(margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 30), child: child);
}
static String getAbstractMessageAsync(V2TimMessage message, List<V2TimGroupMemberFullInfo?> groupMemberList) {
final msgType = message.elemType;
switch (msgType) {
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
return handleCustomMessageString(message);
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
return TIM_t("[语音]");
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
return message.textElem!.text as String;
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return TIM_t("[表情]");
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
final String? option2 = message.fileElem!.fileName ?? "";
return TIM_t_para("[文件] {{option2}}", "[文件] $option2")(option2: option2);
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
return TIM_t("群提示");
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
return TIM_t("[图片]");
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
return TIM_t("[视频]");
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
return TIM_t("[位置]");
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return TIM_t("[聊天记录]");
default:
return TIM_t("未知消息");
}
}
static V2TimImage? getImageFromImgList(List<V2TimImage?>? list, List<String> order) {
V2TimImage? img;
try {
for (String type in order) {
img =
list?.firstWhere((e) => e?.type == HistoryMessageDartConstant.V2_TIM_IMAGE_TYPES[type], orElse: () => null);
}
} catch (e) {
outputLogger.i('getImageFromImgList error ${e.toString()}');
}
return img;
}
static String getDisplayName(V2TimMessage message) {
final friendRemark = message.friendRemark ?? "";
final nameCard = message.nameCard ?? "";
final nickName = message.nickName ?? "";
final sender = message.sender ?? "";
final displayName = friendRemark.isNotEmpty
? friendRemark
: nameCard.isNotEmpty
? nameCard
: nickName.isNotEmpty
? nickName
: sender;
return displayName.toString();
}
static Future<V2TimValueCallback<V2TimMessage>?> handleMessageError(
Future<V2TimValueCallback<V2TimMessage>?> fun, BuildContext context) async {
final res = await fun;
return handleMessageErrorCode(res, context);
}
static V2TimValueCallback<V2TimMessage>? handleMessageErrorCode(
V2TimValueCallback<V2TimMessage>? sendMsgRes, BuildContext context) {
if (sendMsgRes == null) return null;
return sendMsgRes;
}
}
+45
View File
@@ -0,0 +1,45 @@
import 'dart:async';
class OptimizeUtils {
static throttle(Function func, int milliseconds) {
bool enable = true;
return (val) {
if (enable == true) {
enable = false;
Future.delayed(Duration(milliseconds: milliseconds), () {
enable = true;
func(val);
});
}
};
}
static multiThrottle(Function func, int milliseconds) {
bool enable = true;
return (val1, val2) {
if (enable == true) {
enable = false;
Future.delayed(Duration(milliseconds: milliseconds), () {
enable = true;
func(val1, val2);
});
}
};
}
static debounce(
Function func, [
Duration delay = const Duration(milliseconds: 100),
]) {
Timer? timer;
return (val) {
if (timer != null) {
timer?.cancel();
}
timer = Timer(delay, () {
func(val);
});
};
}
}
+366
View File
@@ -0,0 +1,366 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.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/platform.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class PermissionRequestInfo extends StatefulWidget {
final Function removeOverLay;
final int permissionType;
final String appName;
const PermissionRequestInfo(
{Key? key,
required this.removeOverLay,
required this.permissionType,
required this.appName})
: super(key: key);
@override
State<StatefulWidget> createState() => _PermissionRequestInfo();
}
class _PermissionRequestInfo extends TIMUIKitState<PermissionRequestInfo>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
widget.removeOverLay();
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
widget.removeOverLay();
}
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
final permission = {
1: {
"name": TIM_t("相机"),
"icon": "images/chat_permission_icon_camera.png",
"text": TIM_t("为方便您将所拍摄的照片或视频发送给朋友,以及进行视频通话,请允许我们访问摄像头进行拍摄照片和视频。")
},
7: {
"name": TIM_t("麦克风"),
"icon": "images/chat_permission_icon_mic.png",
"text": TIM_t("为方便您发送语音消息、拍摄视频以及音视频通话,请允许我们使用麦克风进行录音。")
},
9: {
"name": TIM_t("相册"),
"icon": "images/chat_permission_icon_file.png",
"text": TIM_t("为方便您查看和选择相册里的图片视频发送给朋友,以及保存内容到设备,请允许我们访问您设备上的照片、媒体内容。")
},
15: {
"name": TIM_t("存储"),
"icon": "images/chat_permission_icon_file.png",
"text": TIM_t("为方便您查看和选择相册里的图片视频发送给朋友,以及保存内容到设备,请允许我们访问您设备上的照片、媒体内容。")
},
32: {
"name": TIM_t("相册"),
"icon": "images/chat_permission_icon_file.png",
"text": TIM_t("为方便您查看和选择相册里的图片视频发送给朋友,以及保存内容到设备,请允许我们访问您设备上的照片、媒体内容。")
},
}[widget.permissionType];
final option2 = permission?["name"] ?? "";
return Stack(
children: [
Positioned(
child: SafeArea(
child: Opacity(
opacity: 0.7,
child: Container(
color: theme.black,
padding: const EdgeInsets.symmetric(horizontal: 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 50,
),
SizedBox(
height: 50,
width: 50,
child: Image.asset(
permission?["icon"] ?? "",
package: "tencent_cloud_chat_uikit",
),
),
const SizedBox(
height: 10,
),
Text(
TIM_t(widget.appName) +
TIM_t_para(" 申请获取{{option2}}", " 申请获取$option2")(
option2: option2) +
TIM_t("权限"),
style: TextStyle(color: theme.white, fontSize: 18),
),
const SizedBox(
height: 20,
),
Text(
permission?["text"] ?? "",
style: TextStyle(color: theme.white, fontSize: 16),
)
],
),
),
),
),
left: 0,
right: 0,
bottom: 0,
top: 0,
)
],
);
}
}
class Permissions {
static OverlayEntry? _entry;
static List<String> _names(BuildContext context) {
return <String>[
TIM_t("日历"),
TIM_t("相机"),
TIM_t("联系人"),
TIM_t("位置"),
'locationAlways',
'locationWhenInUse',
'mediaLibrary',
TIM_t("麦克风"),
'phone',
TIM_t("照片"),
TIM_t("相册写入"),
'reminders',
'sensors',
'sms',
'speech',
TIM_t("文件"),
'ignoreBatteryOptimizations',
'notification',
'access_media_location',
'activity_recognition',
'unknown',
'bluetooth',
'manageExternalStorage',
'systemAlertWindow',
'requestInstallPackages',
'appTrackingTransparency',
'criticalAlerts',
'accessNotificationPolicy',
'bluetoothScan',
'bluetoothAdvertise',
'bluetoothConnect',
'nearbyWifiDevices',
TIM_t("视频"),
'audio',
'scheduleExactAlarm'
];
}
static String _permissionText(
BuildContext context, String appName, int value) {
final _prefix = TIM_t("需要授予");
final _postfixList = <String>[
TIM_t("日历"),
TIM_t(" 相机权限,以正常使用拍摄图片视频、视频通话等功能。"),
TIM_t("联系人"),
TIM_t("位置"),
'locationAlways',
'locationWhenInUse',
'mediaLibrary',
TIM_t(" 麦克风权限,以正常使用发送语音消息、拍摄视频、音视频通话等功能。"),
'phone',
TIM_t(" 访问照片权限,以正常使用发送图片、视频等功能。"),
TIM_t(" 访问相册写入权限,以正常使用存储图片、视频等功能。"),
'reminders',
'sensors',
'sms',
'speech',
TIM_t(" 文件读写权限,以正常使用在聊天功能中的图片查看、选择能力和发送文件的能力。"),
'ignoreBatteryOptimizations',
'notification',
'access_media_location',
'activity_recognition',
'unknown',
'bluetooth',
'manageExternalStorage',
'systemAlertWindow',
'requestInstallPackages',
'appTrackingTransparency',
'criticalAlerts',
'accessNotificationPolicy',
'bluetoothScan',
'bluetoothAdvertise',
'bluetoothConnect',
'nearbyWifiDevices',
TIM_t(" 访问相册中视频权限,以正常使用发送视频等功能。"),
'audio',
'scheduleExactAlarm'
];
return _prefix + appName + _postfixList[value];
}
static Future<bool> checkPermission(
BuildContext context,
int value, [
TUITheme? theme,
bool isShowPermissionPage = true,
]) async {
final status = await Permission.byValue(value).status;
if (status.isGranted || status.isLimited) {
return true;
}
final bool? shouldRequestPermission = await showPermissionConfirmDialog(
context,
value,
theme,
isShowPermissionPage,
);
if (shouldRequestPermission != null && shouldRequestPermission) {
final isGranted = await Permission.byValue(value).request().isGranted;
_entry?.remove();
_entry = null;
return isGranted;
}
return shouldRequestPermission ?? false;
}
static Future<bool> checkPermissionSetBefore(int value) async {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
SharedPreferences prefs = await _prefs;
final _hasPermissionSetBefore = prefs.getBool("permission$value");
return _hasPermissionSetBefore ?? false;
}
static Future<bool> setLocalPermission(int value) async {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
SharedPreferences prefs = await _prefs;
return await prefs.setBool("permission$value", true);
}
static showPermissionRequestInfoDialog(BuildContext context, value) async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
final entry = OverlayEntry(builder: (context) {
return PermissionRequestInfo(
appName: appName,
removeOverLay: () {
_entry?.remove();
_entry = null;
},
permissionType: value,
);
});
_entry = entry;
Overlay.of(context).insert(entry);
}
static Future<bool?> showPermissionConfirmDialog(BuildContext context, value,
[TUITheme? theme, bool isShowPermissionPage = true]) async {
final platformUtils = PlatformUtils();
// 第一次直接走系统文案
if (!await checkPermissionSetBefore(value)) {
await setLocalPermission(value);
if (platformUtils.isAndroid && isShowPermissionPage) {
showPermissionRequestInfoDialog(context, value);
}
return true;
}
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
final option2 = _names(context)[value];
final permissionText = _permissionText(context, appName, value);
void closeDialog() {
Navigator.of(context).pop(false);
}
void getPermission() async {
Navigator.of(context).pop(false);
openAppSettings();
}
return showDialog<bool>(
context: context,
builder: (context) {
return platformUtils.isIOS
? CupertinoAlertDialog(
title: Text("“$appName”" +
TIM_t_para(" 想访问您的{{option2}}", " 想访问您的$option2")(
option2: option2)),
content: Text(permissionText),
actions: <Widget>[
CupertinoDialogAction(
child: Text(TIM_t("以后再说")),
onPressed: closeDialog, // 关闭对话框
),
CupertinoDialogAction(
child: Text(TIM_t("去开启")),
onPressed: getPermission,
),
],
)
: AlertDialog(
content: Text(permissionText),
actions: <Widget>[
const Divider(),
SizedBox(
height: 48,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: TextButton(
child: Text(TIM_t("以后再说"),
style: TextStyle(
color: theme?.black ?? Colors.black,
)),
onPressed: closeDialog, // 关闭对话框
),
),
const VerticalDivider(),
Expanded(
child: TextButton(
child: Text(TIM_t("去开启"),
style: TextStyle(
color: theme?.black ?? Colors.black,
)),
onPressed: getPermission,
),
)
],
),
)
],
);
},
);
}
}
+61
View File
@@ -0,0 +1,61 @@
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
class PlatformUtils {
PlatformUtils._internal();
static late bool _isAndroid;
static late bool _isIos;
static late bool _isMobile;
static late bool _isWeb;
static late bool _isWindows;
static late bool _isMacOS;
static late bool _isLinux;
static late bool _isDesktop;
static bool _isInstantiation = false;
factory PlatformUtils() {
if (!_isInstantiation) {
_isAndroid = !kIsWeb && Platform.isAndroid;
_isIos = !kIsWeb && Platform.isIOS;
_isMobile = _isAndroid || _isIos;
_isWindows = !kIsWeb && Platform.isWindows;
_isMacOS = !kIsWeb && Platform.isMacOS;
_isLinux = !kIsWeb && Platform.isLinux;
_isDesktop = _isMacOS || _isWindows || _isLinux;
_isWeb = kIsWeb;
_isInstantiation = true;
}
return _instance;
}
static late final PlatformUtils _instance = PlatformUtils._internal();
get isAndroid {
return _isAndroid;
}
get isWeb {
return _isWeb;
}
get isIOS {
return _isIos;
}
get isWindows {
return _isWindows;
}
get isMacOS {
return _isMacOS;
}
get isMobile {
return _isMobile;
}
bool get isDesktop => _isDesktop;
bool get isLinux => _isLinux;
}
+24
View File
@@ -0,0 +1,24 @@
import 'package:flutter/cupertino.dart';
class FadeRoute extends PageRouteBuilder {
final Widget page;
FadeRoute({required this.page})
: super(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
page,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
FadeTransition(
opacity: animation,
child: child,
),
);
}
+105
View File
@@ -0,0 +1,105 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:uuid/uuid.dart';
class ScreenshotHelper {
static Future<String?> captureScreen() async {
await requestScreenRecordingPermission();
String directory;
if(PlatformUtils().isWindows){
final String documentsDirectoryPath =
"${Platform.environment['USERPROFILE']}";
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String pkgName = packageInfo.packageName;
directory = p.join(documentsDirectoryPath, "Documents", ".TencentCloudChat",
pkgName, "screenshots");
}else{
final dic = await getApplicationSupportDirectory();
directory = dic.path;
}
const uuid = Uuid();
final fileName = 'screenshot_${uuid.v4()}.png';
final filePath = '$directory/$fileName';
if (Platform.isMacOS) {
// 在macOS平台上使用screencapture工具
final result = await Process.run(
'screencapture',
['-i', '-s', '-o', filePath],
);
if (result.exitCode == 0) {
return filePath;
} else {
return null;
}
} else if (Platform.isWindows) {
// 在Windows平台上使用snippingtool工具
final result = await Process.run(
'snippingtool',
['/clip', filePath],
);
if (result.exitCode == 0) {
return filePath;
} else {
return null;
}
} else {
// 不支持的平台
return null;
}
}
static Future<bool> requestScreenRecordingPermission() async {
if (Platform.isMacOS) {
final result = await Process.run(
'sh', ['-c', 'echo ${Platform.environment['USER']}']);
final username = result.stdout.toString().trim();
const script =
'tell application "System Events" to return exists (processes where name is "ControlCenter")';
final process = await Process.run('osascript', ['-e', script]);
final isControlCenterRunning = process.stdout.toString().trim() == 'true';
if (!isControlCenterRunning) {
await Process.run('open', ['-a', 'ControlCenter']);
await Future.delayed(const Duration(seconds: 1));
}
final script2 = 'tell application "ControlCenter" to activate\n'
'tell application "System Events"\n'
' tell process "ControlCenter"\n'
' click menu item "Screen Recording" of menu "File" of menu bar 1\n'
' delay 0.5\n'
' keystroke "$username" & return\n'
' end tell\n'
'end tell\n';
await Process.run('osascript', ['-e', script2]);
await Future.delayed(const Duration(seconds: 1));
final isScreenRecordingEnabled = await SystemChannels.platform
.invokeMethod<bool>('isScreenRecordingEnabled');
return isScreenRecordingEnabled ?? false;
} else {
return true;
}
}
static Future<Size> getImageSize(String imagePath) async {
final bytes = await File(imagePath).readAsBytes();
final completer = Completer<Size>();
final imageStream =
Image.memory(bytes).image.resolve(ImageConfiguration.empty);
imageStream.addListener(ImageStreamListener((imageInfo, _) {
completer.complete(Size(
imageInfo.image.width.toDouble(), imageInfo.image.height.toDouble()));
}));
return completer.future;
}
}
+63
View File
@@ -0,0 +1,63 @@
// ignore_for_file: constant_identifier_names
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
enum DeviceType { Desktop, Mobile }
class FormFactor {
static double desktop = 900;
static double handset = 300;
}
class TUIKitScreenUtils {
static DeviceType? deviceType;
/// Although specifying the `BuildContext` is optional, providing it can prevent layout issues when this widget renders immediately after the app launch.
/// If this widget needs to be used at the moment the app launches, it's recommended to provide the `BuildContext` here.
static DeviceType getFormFactor([BuildContext? context]) {
if (deviceType != null) return deviceType!;
if (PlatformUtils().isWeb) {
final win = WidgetsBinding.instance.platformDispatcher.views.first;
final size = win.physicalSize;
final screenWidth = size.width / win.devicePixelRatio;
final screenHeight = size.height / win.devicePixelRatio;
final diagonalInInches =
sqrt(pow(screenWidth, 2) + pow(screenHeight, 2)) / 96.0;
deviceType = diagonalInInches < 11.0 ? DeviceType.Mobile : DeviceType.Desktop;
return deviceType ?? DeviceType.Mobile;
}else{
if(context != null){
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHeight = MediaQuery.of(context).size.height;
if (deviceWidth > FormFactor.desktop || deviceWidth > deviceHeight * 1.1) {
deviceType = DeviceType.Desktop;
} else if (deviceWidth > FormFactor.handset) {
deviceType = DeviceType.Mobile;
}
return deviceType ?? DeviceType.Mobile;
}else{
return DeviceType.Mobile;
}
}
}
static Widget getDeviceWidget({
/// Although specifying the `BuildContext` is optional, providing it can prevent layout issues when this widget renders immediately after the app launch.
/// If this widget needs to be used at the moment the app launches, it's recommended to provide the `BuildContext` here.
BuildContext? context,
required Widget defaultWidget,
Widget? desktopWidget,
Widget? mobileWidget,
}) {
deviceType ??= getFormFactor(context);
if (deviceType == DeviceType.Desktop) return desktopWidget ?? defaultWidget;
return mobileWidget ?? defaultWidget;
}
}
+69
View File
@@ -0,0 +1,69 @@
import 'dart:async';
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';
import 'package:just_audio/just_audio.dart';
import 'package:tencent_cloud_chat_uikit/import_proxy/import_proxy.dart';
typedef PlayStateListener = void Function(PlayState playState);
typedef SoundInterruptListener = void Function();
typedef ResponseListener = void Function(RecordResponse recordResponse);
class SoundPlayer {
final ImportProxy importProxy = ImportProxy();
static final FlutterPluginRecord _recorder = FlutterPluginRecord();
static SoundInterruptListener? _soundInterruptListener;
static bool isInit = false;
static final AudioPlayer _audioPlayer = AudioPlayer();
static initSoundPlayer() {
if (!isInit) {
_recorder.init();
// AudioPlayer.global.setGlobalAudioContext(const AudioContext());
isInit = true;
}
}
static Future<void> play({required String url}) async {
_audioPlayer.stop();
if (_soundInterruptListener != null) {
_soundInterruptListener!();
}
await _audioPlayer.setUrl(url);
await _audioPlayer.play();
}
static stop() {
_audioPlayer.stop();
}
static dispose() {
_audioPlayer.dispose();
_recorder.dispose();
}
static StreamSubscription<PlayerState> playStateListener({required void Function(PlayerState)? listener}) => _audioPlayer.playerStateStream.listen(listener);
static setSoundInterruptListener(SoundInterruptListener listener) {
_soundInterruptListener = listener;
}
static removeSoundInterruptListener() {
_soundInterruptListener = null;
}
static StreamSubscription<RecordResponse> responseListener(ResponseListener listener) => _recorder.response.listen(listener);
static StreamSubscription<RecordResponse> responseFromAmplitudeListener(ResponseListener listener) => _recorder.responseFromAmplitude.listen(listener);
static startRecord() {
_recorder.start();
}
static stopRecord() {
_recorder.stop();
}
}
+111
View File
@@ -0,0 +1,111 @@
// ignore_for_file: prefer_typing_uninitialized_variables
import 'package:intl/intl.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
class TimeAgo {
List<String> dayMap() {
return [
TIM_t("昨天"),
TIM_t("前天"),
];
}
List<String> weekdayMap() {
return ['', TIM_t("星期一"), TIM_t("星期二"), TIM_t("星期三"), TIM_t("星期四"), TIM_t("星期五"), TIM_t("星期六"), TIM_t("星期天")];
}
String getYearMonthDate(DateTime dateTime) {
String month = dateTime.month.toString();
String date = dateTime.day.toString();
return dateTime.year.toString() + '/' + (month.length == 1 ? '0' : '') + month + '/' + (date.length == 1 ? '0' : '') + date;
}
String getMonthDate(DateTime dateTime) {
String month = dateTime.month.toString();
String date = dateTime.day.toString();
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);
if (date.isBefore(epochLimit)) {
return null;
}
final Duration duration = DateTime.now().difference(date);
final int diffDays = duration.inDays + (duration.inMinutes > DateTime.now().difference(DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day)).inMinutes ? 1 : 0);
final int diffMinutes = duration.inMinutes;
var res;
// 一个礼拜之内
if (diffDays > 0 && diffDays < 7) {
if (diffDays <= 2) {
res = dayMap()[diffDays - 1];
} else {
res = weekdayMap()[date.weekday];
}
} else if (diffDays >= 7) {
//当年内
if (date.year == DateTime.now().year) {
res = getMonthDate(date);
} else {
res = getYearMonthDate(date);
}
} else {
if (diffMinutes > 1) {
if (diffMinutes < 60) {
final String option2 = diffMinutes.toString();
res = TIM_t_para("{{option2}} 分钟前", "$option2 分钟前")(option2: option2);
} else {
res = "${date.hour}:${date.minute < 10 ? "0" + date.minute.toString() : date.minute}";
// res = "$prefix $timeStr";
}
} else {
res = TIM_t("现在");
}
}
return res;
}
String getTimeForMessage(int timeStamp) {
var nowTime = DateTime.now();
nowTime = DateTime(nowTime.year, nowTime.month, nowTime.day);
var ftime = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000);
// var preFix = ftime.hour >= 12 ? TIM_t("下午") : TIM_t("上午");
final timeStr = DateFormat('HH:mm').format(ftime); // Use 'HH:mm' for 24-hour format
// 一年外 年月日 + 时间 (24小时制)
if (nowTime.year != ftime.year) {
return '${DateFormat('yyyy-MM-dd').format(ftime)} $timeStr';
}
// 一年内一周外 月日 + 时间 (24小时制)
if (ftime.isBefore(nowTime.subtract(const Duration(days: 6)))) {
return '${DateFormat('MM-dd').format(ftime)} $timeStr';
}
// 一周内一天外 星期 + 时间 (24小时制)
if (ftime.isBefore(nowTime.subtract(const Duration(days: 1)))) {
return '${weekdayMap()[ftime.weekday]} $timeStr';
}
// 昨日 昨天 + 时间 (24小时制)
if (nowTime.day != ftime.day) {
String option2 = timeStr;
return TIM_t_para("昨天 {{option2}}", "昨天 $option2")(option2: option2);
}
// 同年月日 时间 (24小时制)
return timeStr;
}
}
@@ -0,0 +1,257 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_friend_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/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/views/TIMUIKitAddFriend/tim_uikit_send_application.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitAddFriend extends StatefulWidget {
final bool? isShowDefaultGroup;
/// You may navigate to user profile page, if friendship relationship exists.
final Function(String userID) onTapAlreadyFriendsItem;
/// The life cycle hooks for adding friends and contact business logic
final AddFriendLifeCycle? lifeCycle;
/// The callback function to close the widget upon completion by the parent component.
final VoidCallback? closeFunc;
const TIMUIKitAddFriend(
{Key? key,
this.isShowDefaultGroup = false,
this.lifeCycle,
required this.onTapAlreadyFriendsItem,
this.closeFunc})
: super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitAddFriendState();
}
class _TIMUIKitAddFriendState extends TIMUIKitState<TIMUIKitAddFriend> {
final TextEditingController _controller = TextEditingController();
final CoreServicesImpl _coreServicesImpl = serviceLocator<CoreServicesImpl>();
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
final TUISelfInfoViewModel _selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
final FocusNode _focusNode = FocusNode();
bool isFocused = false;
bool showResult = false;
List<V2TimUserFullInfo>? searchResult;
Widget _searchResultItemBuilder(V2TimUserFullInfo friendInfo, TUITheme theme) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final faceUrl = friendInfo.faceUrl ?? "";
final userID = friendInfo.userID ?? "";
final String showName =
((friendInfo.nickName != null && friendInfo.nickName!.isNotEmpty) ? friendInfo.nickName : userID) ?? "";
return InkWell(
onTap: () async {
final checkFriend = await _friendshipServices
.checkFriend(userIDList: [userID], checkType: FriendTypeEnum.V2TIM_FRIEND_TYPE_SINGLE);
if (checkFriend != null) {
final res = checkFriend.first;
if (res.resultCode == 0 && res.resultType != 0) {
widget.onTapAlreadyFriendsItem(userID);
return;
}
}
if (userID == _selfInfoViewModel.loginInfo?.userID) {
widget.onTapAlreadyFriendsItem(userID);
return;
}
if (isDesktopScreen) {
if (widget.closeFunc != null) {
widget.closeFunc!();
}
TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.addFriend,
context: context,
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.width * 0.4,
title: TIM_t("添加好友"),
child: (closeFuncSendApplication) => SendApplication(
lifeCycle: widget.lifeCycle,
isShowDefaultGroup: widget.isShowDefaultGroup ?? false,
friendInfo: friendInfo,
model: _selfInfoViewModel),
);
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SendApplication(
lifeCycle: widget.lifeCycle,
isShowDefaultGroup: widget.isShowDefaultGroup ?? false,
friendInfo: friendInfo,
model: _selfInfoViewModel)));
}
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: isDesktopScreen ? 38 : 48,
height: isDesktopScreen ? 38 : 48,
margin: const EdgeInsets.only(right: 16),
child: Avatar(faceUrl: faceUrl, showName: showName),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
showName,
style: TextStyle(color: theme.darkTextColor, fontSize: isDesktopScreen ? 16 : 18),
),
const SizedBox(
height: 4,
),
Text(
"ID: $userID",
style: TextStyle(fontSize: 12, color: theme.weakTextColor),
)
],
),
],
),
),
);
}
List<Widget> _searchResultBuilder(List<V2TimUserFullInfo>? searchResult, TUITheme theme) {
final noResult = searchResult == null || searchResult.isEmpty;
if (noResult) {
return [
Container(
margin: const EdgeInsets.only(top: 20),
child: Center(
child: Text(TIM_t("该用户不存在"), style: TextStyle(color: theme.weakTextColor, fontSize: 14)),
),
)
];
}
return searchResult.map((e) => _searchResultItemBuilder(e, theme)).toList();
}
@override
void initState() {
super.initState();
_focusNode.addListener(() {
final _isFocused = _focusNode.hasFocus;
isFocused = _isFocused;
setState(() {});
});
}
@override
void dispose() {
super.dispose();
}
searchFriend(String params) async {
final response = await _coreServicesImpl.getUsersInfo(userIDList: [params]);
if (response.code == 0) {
setState(() {
searchResult = response.data;
});
} else {
setState(() {
searchResult = null;
});
}
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: _selfInfoViewModel),
],
builder: (BuildContext context, Widget? w) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true,
focusNode: _focusNode,
controller: _controller,
onChanged: (value) {
if (value.trim().isEmpty) {
setState(() {
showResult = false;
});
}
},
textInputAction: TextInputAction.search,
onSubmitted: (_) {
final searchParams = _controller.text;
if (searchParams.trim().isNotEmpty) {
searchFriend(searchParams);
showResult = true;
_focusNode.requestFocus();
setState(() {});
}
},
decoration: InputDecoration(
prefixIcon: Icon(
Icons.search_outlined,
color: theme.weakTextColor,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
contentPadding: EdgeInsets.zero,
hintStyle: TextStyle(
color: theme.weakTextColor,
),
fillColor: theme.inputFillColor,
filled: true,
hintText: TIM_t("搜索用户 ID")),
)),
],
),
),
if (showResult)
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: SingleChildScrollView(
child: Column(
children: _searchResultBuilder(searchResult, theme),
),
),
),
)
],
);
},
);
}
}
@@ -0,0 +1,231 @@
import 'package:flutter/material.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_friend_life_cycle.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/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class SendApplication extends StatefulWidget {
final V2TimUserFullInfo friendInfo;
final TUISelfInfoViewModel model;
final bool? isShowDefaultGroup;
final AddFriendLifeCycle? lifeCycle;
const SendApplication(
{Key? key, this.lifeCycle, required this.friendInfo, required this.model, this.isShowDefaultGroup = false})
: super(key: key);
@override
State<StatefulWidget> createState() => _SendApplicationState();
}
class _SendApplicationState extends TIMUIKitState<SendApplication> {
final TextEditingController _verficationController = TextEditingController();
final TextEditingController _nickNameController = TextEditingController();
@override
void initState() {
super.initState();
final showName = widget.model.loginInfo?.nickName ?? widget.model.loginInfo?.userID;
_verficationController.text = "我是: $showName";
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final FriendshipServices _friendshipServices = serviceLocator<FriendshipServices>();
final faceUrl = widget.friendInfo.faceUrl ?? "";
final userID = widget.friendInfo.userID ?? "";
final String showName = ((widget.friendInfo.nickName != null && widget.friendInfo.nickName!.isNotEmpty)
? widget.friendInfo.nickName
: userID) ??
"";
final option2 = widget.friendInfo.selfSignature ?? "";
Widget sendApplicationBody() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: theme.white,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
margin: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48,
height: 48,
margin: const EdgeInsets.only(right: 12),
child: Avatar(faceUrl: faceUrl, showName: showName),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
showName,
style: TextStyle(color: theme.darkTextColor, fontSize: 18),
),
const SizedBox(
height: 4,
),
Text(
"ID: $userID",
style: TextStyle(fontSize: 13, color: theme.weakTextColor),
),
const SizedBox(
height: 4,
),
if (TencentUtils.checkString(option2) != null)
Text(
TIM_t_para("个性签名: {{option2}}", "个性签名: $option2")(option2: option2),
style: TextStyle(fontSize: 13, color: theme.weakTextColor),
),
],
)
],
),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
TIM_t("填写验证信息"),
style: TextStyle(fontSize: 16, color: theme.weakTextColor),
),
),
Container(
margin: const EdgeInsets.only(top: 6, bottom: 12),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: theme.white,
child: TextField(
// minLines: 1,
maxLines: 4,
controller: _verficationController,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
border: InputBorder.none,
hintStyle: TextStyle(color: theme.textgrey),
hintText: '',
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
TIM_t("请填写备注"),
style: TextStyle(fontSize: 16, color: theme.weakTextColor),
),
),
Container(
color: theme.white,
padding: const EdgeInsets.symmetric(horizontal: 16),
margin: const EdgeInsets.only(top: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
TIM_t("备注"),
style: TextStyle(color: theme.darkTextColor, fontSize: 16),
),
SizedBox(
width: 50,
child: TextField(
controller: _nickNameController,
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
hintStyle: TextStyle(
color: theme.textgrey,
),
hintText: '',
),
),
)
],
),
),
const Divider(
height: 1,
),
if (widget.isShowDefaultGroup == true)
Container(
color: theme.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
TIM_t("分组"),
style: TextStyle(color: theme.darkTextColor, fontSize: 16),
),
Text(
TIM_t("我的好友"),
style: TextStyle(color: theme.darkTextColor, fontSize: 16),
)
],
),
),
Container(
color: theme.white,
width: double.infinity,
margin: const EdgeInsets.only(top: 10),
child: TextButton(
onPressed: () async {
final remark = _nickNameController.text;
final addWording = _verficationController.text;
final friendGroup = TIM_t("我的好友");
if (widget.lifeCycle?.shouldAddFriend != null &&
await widget.lifeCycle!.shouldAddFriend(userID, remark, friendGroup, addWording, context) ==
false) {
return;
}
_friendshipServices.addFriend(
userID: userID,
addType: FriendTypeEnum.V2TIM_FRIEND_TYPE_BOTH,
remark: remark,
addWording: addWording,
friendGroup: friendGroup);
},
child: Text(TIM_t("发送"))),
)
],
),
);
}
return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: Container(
padding: const EdgeInsets.only(top: 10),
color: theme.weakBackgroundColor,
child: sendApplicationBody(),
),
defaultWidget: Scaffold(
appBar: AppBar(
title: Text(
TIM_t("添加好友"),
style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
),
shadowColor: theme.white,
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
iconTheme: IconThemeData(
color: theme.appbarTextColor,
),
),
body: sendApplicationBody(),
));
}
}
@@ -0,0 +1,301 @@
import 'package:flutter/material.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_group_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/conversation/conversation_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitAddGroup/tim_uikit_send_application.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
class TIMUIKitAddGroup extends StatefulWidget {
/// The life cycle hooks for adding group business logic
final AddGroupLifeCycle? lifeCycle;
/// Navigate to group chat, if user is already a member of the current group.
final Function(String groupID, V2TimConversation conversation) onTapExistGroup;
final VoidCallback? closeFunc;
const TIMUIKitAddGroup({Key? key, this.lifeCycle, required this.onTapExistGroup, this.closeFunc}) : super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitAddGroupState();
}
class _TIMUIKitAddGroupState extends TIMUIKitState<TIMUIKitAddGroup> {
final TextEditingController _controller = TextEditingController();
final GroupServices _groupServices = serviceLocator<GroupServices>();
final ConversationService _conversationService = serviceLocator<ConversationService>();
final TUIFriendShipViewModel friendShipViewModel = serviceLocator<TUIFriendShipViewModel>();
List<V2TimGroupInfo>? _addedGroupList;
List<V2TimGroupInfo>? groupResult = [];
final FocusNode _focusNode = FocusNode();
bool isFocused = false;
bool showResult = false;
String _getGroupType(String type) {
String groupType;
switch (type) {
case GroupType.AVChatRoom:
groupType = TIM_t("聊天室");
break;
case GroupType.Meeting:
groupType = TIM_t("会议群");
break;
case GroupType.Public:
groupType = TIM_t("公开群");
break;
case GroupType.Work:
groupType = TIM_t("工作群");
break;
case GroupType.Community:
groupType = TIM_t("社群");
break;
default:
groupType = TIM_t("未知群");
break;
}
return groupType;
}
Widget _searchResultItemBuilder(V2TimGroupInfo groupInfo, TUITheme theme) {
final faceUrl = groupInfo.faceUrl ?? "";
final groupID = groupInfo.groupID;
final showName = groupInfo.groupName ?? groupID;
final groupType = _getGroupType(groupInfo.groupType);
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return InkWell(
onTap: () async {
final V2TimConversation? groupConversation = await getGroupConversation(groupID);
if (groupConversation != null) {
onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("您已是群成员"), infoCode: 6660202));
if (widget.closeFunc != null) {
widget.closeFunc!();
}
return;
}
if (isDesktopScreen) {
if (widget.closeFunc != null) {
widget.closeFunc!();
}
TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.addGroup,
context: context,
width: MediaQuery.of(context).size.width * 0.3,
height: MediaQuery.of(context).size.width * 0.4,
title: TIM_t("添加群聊"),
child: (closeFuncSendApplication) => SendJoinGroupApplication(
lifeCycle: widget.lifeCycle,
groupInfo: groupInfo,
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SendJoinGroupApplication(
lifeCycle: widget.lifeCycle,
groupInfo: groupInfo,
)));
}
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: isDesktopScreen ? 38 : 48,
height: isDesktopScreen ? 38 : 48,
margin: const EdgeInsets.only(right: 16),
child: Avatar(faceUrl: faceUrl, showName: showName),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
showName,
style: TextStyle(fontSize: isDesktopScreen ? 16 : 18),
),
Text(
"ID: $groupID",
style: TextStyle(fontSize: 12, color: theme.weakTextColor),
),
Text(
"群类型: $groupType",
style: TextStyle(fontSize: 12, color: theme.weakTextColor),
)
],
)
],
),
),
);
}
List<Widget> _searchResultBuilder(List<V2TimGroupInfo>? searchResult, TUITheme theme) {
final noResult = searchResult != null && searchResult.isEmpty;
if (noResult) {
return [
Container(
margin: const EdgeInsets.only(top: 20),
child: Center(
child: Text(TIM_t("该群聊不存在"), style: TextStyle(color: theme.weakTextColor, fontSize: 14)),
),
)
];
}
return searchResult?.map((e) => _searchResultItemBuilder(e, theme)).toList() ?? [];
}
Future<V2TimConversation?> getGroupConversation(String groupID) async {
if (_addedGroupList == null || _addedGroupList!.isEmpty) {
_addedGroupList = await _groupServices.getJoinedGroupList();
}
try {
if ((_addedGroupList?.firstWhere((groupItem) {
return groupItem.groupID == groupID;
})) !=
null) {
V2TimConversation? conversation;
conversation = await _conversationService.getConversationListByConversationId(convID: "group_$groupID");
if (conversation == null) {
await friendShipViewModel.loadGroupListData();
if (friendShipViewModel.groupList.indexWhere((element) => element.groupID == groupID) > -1) {
final V2TimGroupInfo groupInfo =
friendShipViewModel.groupList.firstWhere((element) => element.groupID == groupID);
conversation = V2TimConversation(
conversationID: "group_$groupID",
type: 2,
groupID: groupID,
showName: groupInfo.groupName,
groupType: groupInfo.groupType,
);
}
}
return conversation;
}
} catch (e) {
return null;
}
return null;
}
@override
void initState() {
super.initState();
_focusNode.addListener(() {
final _isFocused = _focusNode.hasFocus;
isFocused = _isFocused;
setState(() {});
});
initGroupList();
}
void initGroupList() async {
// Get the joined group list in previous
_addedGroupList = await _groupServices.getJoinedGroupList();
}
@override
void dispose() {
super.dispose();
}
searchGroup(String params) async {
final res = await _groupServices.getGroupsInfo(groupIDList: [params]);
if (res != null) {
setState(() {
groupResult = res.where((e) => e.resultCode == 0).map((e) => e.groupInfo!).toList();
});
} else {
setState(() {
groupResult = [];
});
}
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true,
focusNode: _focusNode,
controller: _controller,
onChanged: (value) {
if (value.trim().isEmpty) {
setState(() {
showResult = false;
});
}
},
textInputAction: TextInputAction.search,
onSubmitted: (_) {
final searchParams = _controller.text;
if (searchParams.trim().isNotEmpty) {
searchGroup(searchParams);
showResult = true;
_focusNode.requestFocus();
setState(() {});
}
},
decoration: InputDecoration(
prefixIcon: Icon(
Icons.search_outlined,
color: theme.weakTextColor,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
contentPadding: EdgeInsets.zero,
hintStyle: TextStyle(
color: theme.weakTextColor,
),
fillColor: theme.inputFillColor,
filled: true,
hintText: TIM_t("搜索群ID")),
)),
],
),
),
if (showResult)
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: SingleChildScrollView(
child: Column(
children: _searchResultBuilder(groupResult, theme),
),
),
),
)
],
);
}
}
@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/add_group_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/core_services_implements.dart';
import 'package:tencent_cloud_chat_uikit/data_services/group/group_services.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class SendJoinGroupApplication extends StatefulWidget {
final V2TimGroupInfo groupInfo;
final AddGroupLifeCycle? lifeCycle;
const SendJoinGroupApplication({Key? key, required this.groupInfo, this.lifeCycle}) : super(key: key);
@override
State<StatefulWidget> createState() => _SendJoinGroupApplicationState();
}
class _SendJoinGroupApplicationState extends TIMUIKitState<SendJoinGroupApplication> {
final TextEditingController _verficationController = TextEditingController();
final GroupServices _groupServices = serviceLocator<GroupServices>();
final CoreServicesImpl _coreServicesImpl = serviceLocator<CoreServicesImpl>();
@override
void initState() {
super.initState();
final loginUserInfo = _coreServicesImpl.loginUserInfo;
final option1 = loginUserInfo?.nickName ?? loginUserInfo?.userID;
_verficationController.text = TIM_t_para("我是: {{option1}}", "我是: $option1")(option1: option1);
}
Future<V2TimCallback?> addGroup(String groupID, String message) async {
if (widget.lifeCycle?.shouldAddGroup != null &&
await widget.lifeCycle!.shouldAddGroup(groupID, message, context) == false) {
return null;
}
return _groupServices.joinGroup(groupID: groupID, message: message);
}
String _getGroupType(String type) {
String groupType;
switch (type) {
case GroupType.AVChatRoom:
groupType = TIM_t("聊天室");
break;
case GroupType.Meeting:
groupType = TIM_t("会议群");
break;
case GroupType.Public:
groupType = TIM_t("公开群");
break;
case GroupType.Work:
groupType = TIM_t("工作群");
break;
case GroupType.Community:
groupType = TIM_t("社群");
break;
default:
groupType = TIM_t("未知群");
break;
}
return groupType;
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final faceUrl = widget.groupInfo.faceUrl ?? "";
final groupID = widget.groupInfo.groupID;
final showName = widget.groupInfo.groupName ?? groupID;
final option1 = _getGroupType(widget.groupInfo.groupType);
Widget sendGroupApplicationBody() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: theme.white,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
margin: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 48,
height: 48,
margin: const EdgeInsets.only(right: 12),
child: Avatar(faceUrl: faceUrl, showName: showName),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
showName,
style: TextStyle(color: theme.darkTextColor, fontSize: 18),
),
const SizedBox(
height: 4,
),
Text(
"ID: $groupID",
style: TextStyle(fontSize: 13, color: theme.weakTextColor),
),
const SizedBox(
height: 4,
),
Text(
TIM_t_para("群类型: {{option1}}", "群类型: $option1")(option1: option1),
style: TextStyle(fontSize: 12, color: theme.weakTextColor),
),
],
)
],
),
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
TIM_t("填写验证信息"),
style: TextStyle(fontSize: 16, color: theme.weakTextColor),
),
),
Container(
margin: const EdgeInsets.only(top: 6, bottom: 12),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
color: theme.white,
child: TextField(
maxLines: 4,
controller: _verficationController,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
border: InputBorder.none,
hintStyle: TextStyle(
color: theme.textgrey,
),
hintText: '')),
),
Container(
color: theme.white,
width: double.infinity,
margin: const EdgeInsets.only(top: 10),
child: TextButton(
onPressed: () async {
final addWording = _verficationController.text;
final res = await addGroup(groupID, addWording);
if (res?.code == 0) {
onTIMCallback(TIMCallback(
type: TIMCallbackType.INFO, infoRecommendText: TIM_t("群申请已发送"), infoCode: 6660201));
}
},
child: Text(TIM_t("发送"))),
)
],
),
);
}
return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: sendGroupApplicationBody(),
defaultWidget: Scaffold(
appBar: AppBar(
title: Text(
TIM_t("进群申请"),
style: TextStyle(color: theme.appbarTextColor, fontSize: 17),
),
shadowColor: theme.white,
backgroundColor: theme.appbarBgColor ?? theme.primaryColor,
iconTheme: IconThemeData(
color: theme.appbarTextColor,
),
),
body: sendGroupApplicationBody(),
));
}
}
@@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import 'package:flutter_slidable_plus_plus/flutter_slidable_plus_plus.dart';
import 'package:provider/provider.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/block_list_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_friendship_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme_view_model.dart';
typedef BlackListItemBuilder = Widget Function(BuildContext context, V2TimFriendInfo friendInfo);
class TIMUIKitBlackList extends StatefulWidget {
final void Function(V2TimFriendInfo friendInfo)? onTapItem;
final Widget Function(BuildContext context)? emptyBuilder;
final BlackListItemBuilder? itemBuilder;
/// The life cycle hooks for block list business logic
final BlockListLifeCycle? lifeCycle;
const TIMUIKitBlackList({Key? key, this.onTapItem, this.emptyBuilder, this.itemBuilder, this.lifeCycle})
: super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitBlackListState();
}
class _TIMUIKitBlackListState extends TIMUIKitState<TIMUIKitBlackList> {
final TUIFriendShipViewModel _friendshipViewModel = serviceLocator<TUIFriendShipViewModel>();
_getShowName(V2TimFriendInfo item) {
final friendRemark = item.friendRemark ?? "";
final nickName = item.userProfile?.nickName ?? "";
final userID = item.userID;
final showName = nickName != "" ? nickName : userID;
return friendRemark != "" ? friendRemark : showName;
}
Widget _itemBuilder(BuildContext context, V2TimFriendInfo friendInfo) {
final theme = Provider.of<TUIThemeViewModel>(context).theme;
final showName = _getShowName(friendInfo);
final faceUrl = friendInfo.userProfile?.faceUrl ?? "";
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
Widget itemWidget() {
return Material(
color: theme.wideBackgroundColor,
child: InkWell(
onTap: () {
if (widget.onTapItem != null) {
widget.onTapItem!(friendInfo);
}
},
child: Container(
padding: const EdgeInsets.only(top: 10, left: 16, right: 16),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: theme.weakDividerColor ?? CommonColor.weakDividerColor))),
child: Row(
children: [
Container(
padding: const EdgeInsets.only(bottom: 12),
margin: const EdgeInsets.only(right: 12),
child: SizedBox(
height: isDesktopScreen ? 30 : 40,
width: isDesktopScreen ? 30 : 40,
child: Avatar(faceUrl: faceUrl, showName: showName),
),
),
Expanded(
child: Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: Text(
showName,
style: TextStyle(color: theme.black, fontSize: isDesktopScreen ? 14 : 18),
),
)),
if (isDesktopScreen)
OutlinedButton(
onPressed: () {
_friendshipViewModel.deleteFromBlockList([friendInfo.userID]);
},
child: Text(
TIM_t("移出黑名单"),
style: TextStyle(color: theme.primaryColor),
))
],
),
),
),
);
}
return TUIKitScreenUtils.getDeviceWidget(
context: context,
desktopWidget: itemWidget(),
defaultWidget: Slidable(
endActionPane: ActionPane(motion: const DrawerMotion(), children: [
SlidableAction(
onPressed: (context) async {
await _friendshipViewModel.deleteFromBlockList([friendInfo.userID]);
},
backgroundColor: theme.cautionColor ?? CommonColor.cautionColor,
foregroundColor: theme.white,
label: TIM_t("删除"),
autoClose: true,
)
]),
child: itemWidget(),
));
}
BlackListItemBuilder _getItemBuilder() {
return widget.itemBuilder ?? _itemBuilder;
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: _friendshipViewModel),
],
builder: (BuildContext context, Widget? w) {
final model = Provider.of<TUIFriendShipViewModel>(context);
model.blockListLifeCycle = widget.lifeCycle;
final blockList = model.blockList;
if (blockList.isNotEmpty) {
return ListView.builder(
shrinkWrap: true,
itemCount: blockList.length,
itemBuilder: (context, index) {
final friendInfo = blockList[index];
final itemBuilder = _getItemBuilder();
return itemBuilder(context, friendInfo);
},
);
}
if (widget.emptyBuilder != null) {
return widget.emptyBuilder!(context);
}
return Container();
},
);
}
}
@@ -0,0 +1,63 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/TIMUIKitTongue/tim_uikit_tongue_item.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
enum MessageListTongueType {
none,
toLatest,
showUnread,
atMe,
showPrevious,
atAll,
}
typedef TongueItemBuilder = Widget Function(
VoidCallback onClick, MessageListTongueType valueType, int unreadCount);
class TIMUIKitHistoryMessageListTongue extends TIMUIKitStatelessWidget {
/// the value type currently
final MessageListTongueType valueType;
/// the callback after clicking
final VoidCallback onClick;
/// unread amount currently
final int unreadCount;
/// the builder for tongue item
final TongueItemBuilder? tongueItemBuilder;
/// total amount of messages at me
final String atNum;
final int previousCount;
TIMUIKitHistoryMessageListTongue({
Key? key,
required this.valueType,
required this.onClick,
required this.previousCount,
required this.unreadCount,
this.tongueItemBuilder,
this.atNum = "",
}) : super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
late Widget tongueItem;
if (tongueItemBuilder != null) {
tongueItem = tongueItemBuilder!(onClick, valueType, unreadCount);
} else {
tongueItem = TIMUIKitTongueItem(
onClick: onClick,
unreadCount: unreadCount,
valueType: valueType,
atNum: atNum,
previousCount: previousCount,
);
}
return valueType != MessageListTongueType.none ? tongueItem : Container();
}
}
@@ -0,0 +1,182 @@
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_at_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_at_info.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/common_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/TIMUIKitTongue/tim_uikit_chat_history_message_list_tongue.dart';
import 'package:tuple/tuple.dart';
class TIMUIKitHistoryMessageListTongueContainer extends StatefulWidget {
final Widget Function(void Function(), MessageListTongueType, int)? tongueItemBuilder;
final List<V2TimGroupAtInfo?>? groupAtInfoList;
final Function(String targetSeq) scrollToIndexBySeq;
final AutoScrollController scrollController;
final TUIChatSeparateViewModel model;
final V2TimConversation conversation;
const TIMUIKitHistoryMessageListTongueContainer({
Key? key,
this.tongueItemBuilder,
this.groupAtInfoList,
required this.conversation,
required this.scrollToIndexBySeq,
required this.scrollController,
required this.model,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitHistoryMessageListTongueContainerState();
}
class _TIMUIKitHistoryMessageListTongueContainerState extends TIMUIKitState<TIMUIKitHistoryMessageListTongueContainer> {
bool isFinishJumpToAt = false;
List<V2TimGroupAtInfo?>? groupAtInfoList = [];
final TUIChatGlobalModel globalModel = serviceLocator<TUIChatGlobalModel>();
bool isClickShowPrevious = false;
@override
void initState() {
super.initState();
initScrollListener();
groupAtInfoList = widget.groupAtInfoList?.reversed.toList();
}
void changePositionState(HistoryMessagePosition newPosition) {
if (globalModel.getMessageListPosition(widget.model.conversationID) != newPosition) {
globalModel.setMessageListPosition(widget.model.conversationID, newPosition);
}
}
scrollHandler() {
final screenHeight = MediaQuery.of(context).size.height;
final offset = widget.scrollController.offset;
final conversationUnreadCount = widget.model.getConversationUnreadCount();
if (offset <= 0.0 && conversationUnreadCount != 0) {
widget.model.showLatestUnread();
}
if (widget.scrollController.offset <= widget.scrollController.position.minScrollExtent &&
!widget.scrollController.position.outOfRange &&
!widget.model.haveMoreLatestData) {
changePositionState(HistoryMessagePosition.bottom);
} else if (widget.scrollController.offset <= screenHeight * 1.6 &&
widget.scrollController.offset > 0 &&
!widget.scrollController.position.outOfRange &&
!widget.model.haveMoreLatestData) {
changePositionState(HistoryMessagePosition.inTwoScreen);
} else if (widget.scrollController.offset > screenHeight * 1.6 &&
!widget.scrollController.position.outOfRange &&
!widget.model.haveMoreLatestData) {
changePositionState(HistoryMessagePosition.awayTwoScreen);
}
}
void initScrollListener() {
widget.scrollController.addListener(scrollHandler);
}
MessageListTongueType _getTongueValueType(List<V2TimGroupAtInfo?>? groupAtInfoList) {
if (globalModel.getMessageListPosition(widget.model.conversationID) == HistoryMessagePosition.notShowLatest) {
return MessageListTongueType.none;
}
if (groupAtInfoList != null && groupAtInfoList.isNotEmpty && !isFinishJumpToAt) {
if (groupAtInfoList[0]!.atType == 1) {
return MessageListTongueType.atMe;
} else {
return MessageListTongueType.atAll;
}
}
// if ((widget.conversation.unreadCount ?? 0) > 20 && !isClickShowPrevious) {
// return MessageListTongueType.showPrevious;
// }
if (globalModel.unreadCountForTongue > 0) {
return MessageListTongueType.showUnread;
}
if (globalModel.getMessageListPosition(widget.model.conversationID) == HistoryMessagePosition.awayTwoScreen) {
return MessageListTongueType.toLatest;
}
return MessageListTongueType.none;
}
@override
void dispose() {
super.dispose();
widget.scrollController.removeListener(scrollHandler);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return Selector<TUIChatGlobalModel, Tuple2<HistoryMessagePosition, int>>(
builder: (context, value, child) {
return Positioned(
bottom: _getTongueValueType(groupAtInfoList) != MessageListTongueType.showPrevious ? 16 : null,
top: _getTongueValueType(groupAtInfoList) == MessageListTongueType.showPrevious ? 16 : null,
right: 16,
child: TIMUIKitHistoryMessageListTongue(
previousCount: widget.conversation.unreadCount ?? 0,
tongueItemBuilder: widget.tongueItemBuilder,
unreadCount: value.item2,
onClick: () async {
if (groupAtInfoList != null && groupAtInfoList!.isNotEmpty) {
if (groupAtInfoList?.length == 1) {
widget.scrollToIndexBySeq(groupAtInfoList![0]!.seq);
setState(() {
groupAtInfoList = [];
isFinishJumpToAt = true;
});
} else {
widget.scrollToIndexBySeq(groupAtInfoList!.removeAt(0)!.seq);
}
} else if ((widget.conversation.unreadCount ?? 0) > 20 && !isClickShowPrevious) {
try {
isClickShowPrevious = true;
final String? lastSeqString = widget.conversation.lastMessage?.seq;
final int? lastSeq =
TencentUtils.checkString(lastSeqString) != null ? int.parse(lastSeqString!) : null;
final int? previousCount = widget.conversation.unreadCount;
if (lastSeq != null && previousCount != null) {
final targetSeq = lastSeq - previousCount;
await widget.model.loadListForSpecificMessage(seq: targetSeq);
// Future.delayed(const Duration(milliseconds: 100), () {
// widget.scrollToIndexBySeq((targetSeq).toString());
// });
}
} catch (e) {
// TODO: 这里后续加个弹窗提示客户,找消息失败了
}
// widget.model.loadListForSpecificMessage(seq: count);
} else if (value.item1 == HistoryMessagePosition.awayTwoScreen || value.item2 > 0) {
widget.model.showLatestUnread();
widget.scrollController.animateTo(
widget.scrollController.position.minScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
return;
}
},
atNum: groupAtInfoList?.length.toString() ?? "",
valueType: _getTongueValueType(groupAtInfoList),
),
);
},
selector: (c, model) {
final messageListPosition = model.getMessageListPosition(widget.model.conversationID);
final unreadCountForConversation = model.unreadCountForTongue;
return Tuple2(messageListPosition, unreadCountForConversation);
},
);
}
}
@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/TIMUIKitTongue/tim_uikit_chat_history_message_list_tongue.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitTongueItem extends TIMUIKitStatelessWidget {
/// the callback after clicking
final VoidCallback onClick;
/// the value type currently
final MessageListTongueType valueType;
/// unread amount currently
final int unreadCount;
/// total amount of messages at me
final String atNum;
final int previousCount;
TIMUIKitTongueItem({
Key? key,
required this.onClick,
required this.valueType,
required this.previousCount,
required this.unreadCount,
required this.atNum,
}) : super(key: key);
Map<MessageListTongueType, String> textType(BuildContext context) {
final option1 = unreadCount.toString();
final option2 = atNum.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.toLatest: TIM_t("回到最新位置"),
MessageListTongueType.showUnread:
TIM_t_para("{{option1}}条新消息", "$option1条新消息")(option1: option1),
MessageListTongueType.atMe: atMeString,
MessageListTongueType.atAll: TIM_t("@所有人"),
};
}
final Map<MessageListTongueType, IconData> iconType = {
MessageListTongueType.toLatest: Icons.arrow_downward_outlined,
MessageListTongueType.showUnread: Icons.arrow_downward_outlined,
MessageListTongueType.atMe: Icons.arrow_upward_outlined,
MessageListTongueType.atAll: Icons.arrow_upward_outlined,
MessageListTongueType.showPrevious: Icons.arrow_upward_outlined,
};
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return GestureDetector(
onTap: onClick,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: hexToColor("E5E5E5"), width: 1),
boxShadow: [
BoxShadow(
color: theme.weakDividerColor ?? hexToColor("E6E9EB"),
offset: const Offset(0.0, 0.0),
blurRadius: 10,
spreadRadius: 2),
],
),
padding: const EdgeInsets.all(10),
// width: 112,
// height: 37,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(right: 6),
child: Icon(
iconType[valueType],
color: theme.primaryColor,
size: 12,
),
),
Text(
textType(context)[valueType] ?? "",
style: TextStyle(
color: theme.primaryColor,
fontSize: 12.0,
fontWeight: FontWeight.w400),
),
],
),
),
);
}
}
@@ -0,0 +1,531 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:provider/provider.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_at_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_at_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/logger.dart';
// ignore: unused_import
import 'package:tencent_cloud_chat_uikit/ui/utils/optimize_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_config.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/keepalive_wrapper.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'TIMUIKitTongue/tim_uikit_chat_history_message_list_tongue.dart';
import 'TIMUIKitTongue/tim_uikit_chat_history_message_list_tongue_container.dart';
enum LoadingPlace {
none,
top,
bottom,
}
enum ScrollType { toIndex, toIndexBegin }
class TIMUIKitHistoryMessageListController extends ChangeNotifier {
AutoScrollController? scrollController = AutoScrollController();
late ScrollType scrollType;
late V2TimMessage targetMessage;
TIMUIKitHistoryMessageListController({
AutoScrollController? scrollController,
}) {
if (scrollController != null) {
this.scrollController = scrollController;
}
}
scrollToIndex(V2TimMessage message) {
scrollType = ScrollType.toIndex;
targetMessage = message;
notifyListeners();
}
scrollToIndexBegin(V2TimMessage message) {
scrollType = ScrollType.toIndexBegin;
targetMessage = message;
notifyListeners();
}
}
class TIMUIKitHistoryMessageList extends StatefulWidget {
/// message list
final List<V2TimMessage?> messageList;
/// tongue item builder
final TongueItemBuilder? tongueItemBuilder;
/// group at info, it can get from conversation info
final List<V2TimGroupAtInfo?>? groupAtInfoList;
/// use for build message item
final Widget Function(BuildContext, V2TimMessage?)? itemBuilder;
/// can controll message list scroll
final TIMUIKitHistoryMessageListController? controller;
/// use for message jump, if passed will jump to target message.
final V2TimMessage? initFindingMsg;
/// use for load more message
final Future<bool> Function(String?, LoadDirection direction, [int?, int?]) onLoadMore;
/// configuration for list view
final TIMUIKitHistoryMessageListConfig? mainHistoryListConfig;
final TUIChatSeparateViewModel model;
final bool isAllowScroll;
final V2TimConversation conversation;
const TIMUIKitHistoryMessageList(
{Key? key,
required this.model,
required this.messageList,
this.itemBuilder,
this.controller,
required this.onLoadMore,
this.tongueItemBuilder,
this.groupAtInfoList,
this.initFindingMsg,
this.isAllowScroll = true,
this.mainHistoryListConfig,
required this.conversation})
: super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitHistoryMessageListState();
}
class _TIMUIKitHistoryMessageListState extends TIMUIKitState<TIMUIKitHistoryMessageList> {
V2TimMessage? findingMsg;
String findingSeq = "";
late TIMUIKitHistoryMessageListController _controller;
late AutoScrollController _autoScrollController;
LoadingPlace loadingPlace = LoadingPlace.none;
bool maybeHaveMoreMessageForFind = true;
@override
void initState() {
super.initState();
_controller = widget.controller ?? TIMUIKitHistoryMessageListController();
_autoScrollController = _controller.scrollController ?? AutoScrollController();
_controller.addListener(_controllerListener);
initFinding();
}
initFinding() async {
if (widget.initFindingMsg != null) {
await widget.onLoadMore(null, LoadDirection.previous);
setState(() {
findingMsg = widget.initFindingMsg!;
});
}
}
_controllerListener() {
final scrollType = _controller.scrollType;
final targetMessage = _controller.targetMessage;
switch (scrollType) {
case ScrollType.toIndex:
_onScrollToIndex(targetMessage);
break;
case ScrollType.toIndexBegin:
_onScrollToIndexBegin(targetMessage);
break;
default:
}
}
Widget _getMessageItemBuilder(V2TimMessage? messageItem) {
if (widget.itemBuilder != null) {
return widget.itemBuilder!(context, messageItem);
}
return Container();
}
_getMessageId(int index) {
if (widget.messageList[index]!.elemType == 11) {
return _getMessageId(index - 1);
}
return widget.messageList[index]!.msgID;
}
void showCantFindMsg() {
findingMsg = null;
findingSeq = "";
loadingPlace = LoadingPlace.none;
onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("无法定位到原消息"), infoCode: 6660401));
}
_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;
final lastTimestamp = widget.messageList[widget.messageList.length - 1]?.timestamp;
final msgList = widget.messageList;
final targetTimeStamp = targetMsg.timestamp!;
if (targetTimeStamp >= lastTimestamp!) {
// 当前列表里应该有这个消息,试试能不能直接定位到那去
bool isFound = false;
int targetIndex = 1;
for (int i = msgList.length - 1; i >= 0; i--) {
final currentMsg = msgList[i];
if (currentMsg?.timestamp == targetTimeStamp &&
currentMsg?.elemType != 11 &&
currentMsg!.msgID == targetMsg.msgID) {
// find the target index by timestamp and msgID
isFound = true;
targetIndex = -i;
break;
}
}
if (isFound && targetIndex != 1) {
findingMsg = null;
_autoScrollController.scrollToIndex(
targetIndex,
preferPosition: AutoScrollPosition.middle,
);
// execute twice for accurate position, as the position located firstly can be wrong
_autoScrollController.scrollToIndex(targetIndex, preferPosition: AutoScrollPosition.middle);
_autoScrollController.scrollToIndex(targetIndex, preferPosition: AutoScrollPosition.middle);
widget.model.jumpMsgID = targetMsg.msgID!;
loadingPlace = LoadingPlace.none;
} else {
showCantFindMsg();
}
} else {
if (maybeHaveMoreMessageForFind) {
// if the target message not in current message list, load more
findingMsg = targetMsg;
final lastMsgId = _getMessageId(widget.messageList.length - 1);
maybeHaveMoreMessageForFind = await widget.onLoadMore(lastMsgId, LoadDirection.previous, singleLoadAmount);
} else {
showCantFindMsg();
}
}
}
_onScrollToIndexBySeq(String targetSeq) async {
// This method called by tongue request jumping to target @ message
loadingPlace = LoadingPlace.top;
// const int singleLoadAmount = 40;
final msgList = widget.messageList;
String lastSeq = "";
for (int i = msgList.length - 1; i >= 0; i--) {
final currentMsg = msgList[i];
if (currentMsg!.seq != null && currentMsg.seq != "") {
lastSeq = currentMsg.seq!;
break;
}
}
if (int.parse(lastSeq) <= int.parse(targetSeq)) {
bool isFound = false;
int targetIndex = 1;
String? targetMsgID = "";
for (int i = msgList.length - 1; i >= 0; i--) {
final currentMsg = msgList[i];
if (currentMsg?.seq == targetSeq) {
isFound = true;
targetMsgID = currentMsg?.msgID;
targetIndex = -i;
break;
}
}
if (isFound && targetIndex != 1) {
findingSeq = "";
_autoScrollController.scrollToIndex(
targetIndex,
preferPosition: AutoScrollPosition.middle,
);
_autoScrollController.scrollToIndex(targetIndex, preferPosition: AutoScrollPosition.middle);
if (targetMsgID != null && targetMsgID != "") {
widget.model.jumpMsgID = targetMsgID;
}
loadingPlace = LoadingPlace.none;
} else {
showCantFindMsg();
}
} else {
if (maybeHaveMoreMessageForFind) {
findingSeq = targetSeq;
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();
}
}
}
_onScrollToIndexBegin(V2TimMessage targetMsg) {
final lastTimestamp = widget.messageList[widget.messageList.length - 1]?.timestamp;
final msgList = widget.messageList;
final int targetTimeStamp = targetMsg.timestamp!;
if (targetTimeStamp >= lastTimestamp!) {
bool isFound = false;
int targetIndex = 1;
for (int i = msgList.length - 1; i >= 0; i--) {
final currentMsg = msgList[i];
if (currentMsg?.timestamp == targetTimeStamp &&
currentMsg?.elemType != 11 &&
currentMsg!.msgID == targetMsg.msgID) {
isFound = true;
targetIndex = -i;
break;
}
}
if (isFound && targetIndex != 1) {
_autoScrollController.scrollToIndex(
targetIndex,
preferPosition: AutoScrollPosition.end,
);
}
}
}
List<V2TimMessage?> _getReceivedMessageList(int receivedMessageListCount) {
if (receivedMessageListCount == 0) {
return [];
}
final haveTimeStampMessage = widget.messageList[receivedMessageListCount]?.elemType == 11;
final endPoint = haveTimeStampMessage ? receivedMessageListCount + 1 : receivedMessageListCount;
return widget.messageList.sublist(0, endPoint).reversed.toList();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
// center key should generate everytime when build method called.
final GlobalKey centerKey = GlobalKey();
final theme = value.theme;
if (widget.messageList.isEmpty) {
return Container();
}
final messageList = widget.messageList;
final globalModel = context.read<TUIChatGlobalModel>();
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 {
final msgID = TIMUIKitChatUtils.getMessageIDWithinIndex(readMessageList, index);
await widget.onLoadMore(msgID, direction);
}, 20);
final throttleFunctionWithMsgID = OptimizeUtils.multiThrottle((msgID, LoadDirection direction) async {
await widget.onLoadMore(msgID, direction);
}, 200);
if (findingMsg != null) {
_onScrollToIndex(findingMsg!);
} else if (findingSeq != "") {
_onScrollToIndexBySeq(findingSeq);
}
String getMessageIdentifier(V2TimMessage? message, int index) {
return "${message?.msgID} - ${message?.timestamp} - ${message?.seq} -${message?.id ?? ""}";
}
return Stack(
alignment: Alignment.topCenter,
children: [
Scrollbar(
controller: _autoScrollController,
child: CustomScrollView(
center: shouldShowUnreadMessage ? centerKey : null,
key: widget.mainHistoryListConfig?.key,
primary: widget.mainHistoryListConfig?.primary,
physics: (widget.isAllowScroll == false)
? const NeverScrollableScrollPhysics()
: widget.mainHistoryListConfig?.physics,
// padding: widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
// itemExtent: widget.mainHistoryListConfig?.itemExtent,
// prototypeItem: widget.mainHistoryListConfig?.prototypeItem,
cacheExtent: widget.mainHistoryListConfig?.cacheExtent ?? 1500,
semanticChildCount: widget.mainHistoryListConfig?.semanticChildCount,
dragStartBehavior: widget.mainHistoryListConfig?.dragStartBehavior ?? DragStartBehavior.start,
keyboardDismissBehavior:
widget.mainHistoryListConfig?.keyboardDismissBehavior ?? ScrollViewKeyboardDismissBehavior.manual,
restorationId: widget.mainHistoryListConfig?.restorationId,
clipBehavior: widget.mainHistoryListConfig?.clipBehavior ?? Clip.hardEdge,
reverse: true,
shrinkWrap: !shouldShowUnreadMessage,
controller: _autoScrollController,
slivers: [
SliverPadding(
padding: widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final messageItem = unreadMessageList[index];
if (index == unreadMessageList.length - 1 && widget.model.haveMoreLatestData == true) {
throttleFunctionWithMsgID(messageItem?.msgID ?? "", LoadDirection.latest);
}
outputLogger.i(
"Rendering a unread message: ${getMessageIdentifier(messageItem, 0)}, message Type: ${messageItem?.elemType}");
return AutoScrollTag(
controller: _autoScrollController,
index: -index,
key: ValueKey(getMessageIdentifier(messageItem, index)),
highlightColor: Colors.black.withOpacity(0.1),
child: KeepAliveWrapper(
keepAlive: messageItem?.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND,
child: Container(child: _getMessageItemBuilder(messageItem))),
);
},
childCount: unreadMessageList.length,
findChildIndexCallback: (Key key) {
final ValueKey<String> valueKey = key as ValueKey<String>;
final String data = valueKey.value;
final int index =
unreadMessageList.indexWhere((element) => getMessageIdentifier(element, 0) == data);
return index != -1 ? index : null;
})),
),
SliverPadding(
padding: EdgeInsets.zero,
key: centerKey,
),
SliverPadding(
padding: widget.mainHistoryListConfig?.padding ?? EdgeInsets.zero,
sliver: Selector<TUIChatSeparateViewModel, bool>(
selector: (context, model) {
return model.haveMoreData;
},
shouldRebuild: (previous, next) {
return previous != next;
},
builder: (context, haveMoreData, child) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final messageItem = readMessageList[index];
if (index == readMessageList.length - 1) {
if (haveMoreData) {
final lastMessage = globalModel
.messageListMap[TencentUtils.checkString(widget.conversation.groupID) ??
widget.conversation.userID ??
widget.conversation.conversationID]
?.last;
if (lastMessage != null) {
throttleFunctionWithMsgID(lastMessage.msgID ?? "", LoadDirection.previous);
} else {
throttleFunction(index, messageList);
}
return Column(
children: [
LoadingAnimationWidget.staggeredDotsWave(
color: theme.weakTextColor ?? Colors.grey,
size: 28,
),
AutoScrollTag(
controller: _autoScrollController,
index: -index,
key: ValueKey(getMessageIdentifier(messageItem, index)),
highlightColor: Colors.black.withOpacity(0.1),
child: KeepAliveWrapper(
keepAlive: messageItem?.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND,
child: Container(child: _getMessageItemBuilder(messageItem))),
),
],
);
}
}
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}");
return AutoScrollTag(
controller: _autoScrollController,
index: -index,
key: ValueKey(getMessageIdentifier(messageItem, index)),
highlightColor: Colors.black.withOpacity(0.1),
child: KeepAliveWrapper(
keepAlive: messageItem?.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND,
child: Container(child: _getMessageItemBuilder(messageItem))),
);
},
childCount: readMessageList.length,
findChildIndexCallback: (Key key) {
final ValueKey<String> valueKey = key as ValueKey<String>;
final String data = valueKey.value;
final int index =
readMessageList.indexWhere((element) => getMessageIdentifier(element, 0) == data);
return index > -1 ? index : null;
}));
},
),
),
],
)),
TIMUIKitHistoryMessageListTongueContainer(
conversation: widget.conversation,
model: widget.model,
scrollController: _autoScrollController,
scrollToIndexBySeq: _onScrollToIndexBySeq,
groupAtInfoList: widget.groupAtInfoList,
tongueItemBuilder: widget.tongueItemBuilder,
),
if (loadingPlace == LoadingPlace.top)
Positioned(
top: 8,
child: LoadingAnimationWidget.staggeredDotsWave(
color: theme.weakTextColor ?? Colors.grey,
size: 28,
),
),
],
);
}
}
class TIMUIKitHistoryMessageListSelector extends TIMUIKitStatelessWidget {
final Widget Function(BuildContext, List<V2TimMessage?>, Widget?) builder;
final String conversationID;
TIMUIKitHistoryMessageListSelector({Key? key, required this.builder, required this.conversationID}) : super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
return Selector<TUIChatGlobalModel, List<V2TimMessage?>>(
builder: builder,
shouldRebuild: (previous, next) {
final isEquals = const DeepCollectionEquality.unordered().equals(previous, next);
return !isEquals;
},
selector: (context, model) {
final messageList = model.getMessageList(conversationID) ?? [];
return messageList;
});
}
}
@@ -0,0 +1,171 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
// The definition of the following parameters is the same with [ListView.builder]
class TIMUIKitHistoryMessageListConfig {
Key? key;
/// {@template flutter.widgets.scroll_view.scrollDirection}
/// The axis along which the scroll view scrolls.
///
/// Defaults to [Axis.vertical].
/// {@endtemplate}
Axis? scrollDirection;
/// {@template flutter.widgets.scroll_view.primary}
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// When this is true, the scroll view is scrollable even if it does not have
/// sufficient content to actually scroll. Otherwise, by default the user can
/// only scroll the view if it has sufficient content. See [physics].
///
/// Also when true, the scroll view is used for default [ScrollAction]s. If a
/// ScrollAction is not handled by an otherwise focused part of the application,
/// the ScrollAction will be evaluated using this scroll view, for example,
/// when executing [Shortcuts] key events like page up and down.
///
/// On iOS, this also identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
/// {@endtemplate}
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
bool? primary;
/// {@template flutter.widgets.scroll_view.physics}
/// How the scroll view should respond to user input.
///
/// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view.
///
/// Defaults to matching platform conventions. Furthermore, if [primary] is
/// false, then the user cannot scroll if there is insufficient content to
/// scroll, while if [primary] is true, they can always attempt to scroll.
///
/// To force the scroll view to always be scrollable even if there is
/// insufficient content, as if [primary] was true but without necessarily
/// setting it to true, provide an [AlwaysScrollableScrollPhysics] physics
/// object, as in:
///
/// ```dart
/// physics: const AlwaysScrollableScrollPhysics(),
/// ```
///
/// To force the scroll view to use the default platform conventions and not
/// be scrollable if there is insufficient content, regardless of the value of
/// [primary], provide an explicit [ScrollPhysics] object, as in:
///
/// ```dart
/// physics: const ScrollPhysics(),
/// ```
///
/// The physics can be changed dynamically (by providing a new object in a
/// subsequent build), but new physics will only take effect if the _class_ of
/// the provided object changes. Merely constructing a new instance with a
/// different configuration is insufficient to cause the physics to be
/// reapplied. (This is because the final object used is generated
/// dynamically, which can be relatively expensive, and it would be
/// inefficient to speculatively create this object each frame to see if the
/// physics should be updated.)
/// {@endtemplate}
///
/// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the
/// [ScrollPhysics] provided by that behavior will take precedence after
/// [physics].
ScrollPhysics? physics;
/// The amount of space by which to inset the children.
EdgeInsetsGeometry? padding;
/// {@template flutter.widgets.list_view.itemExtent}
/// If non-null, forces the children to have the given extent in the scroll
/// direction.
///
/// Specifying an [itemExtent] is more efficient than letting the children
/// determine their own extent because the scrolling machinery can make use of
/// the foreknowledge of the children's extent to save work, for example when
/// the scroll position changes drastically.
///
/// See also:
///
/// * [SliverFixedExtentList], the sliver used internally when this property
/// is provided. It constrains its box children to have a specific given
/// extent along the main axis.
/// * The [prototypeItem] property, which allows forcing the children's
/// extent to be the same as the given widget.
/// {@endtemplate}
double? itemExtent;
/// {@template flutter.widgets.list_view.prototypeItem}
/// If non-null, forces the children to have the same extent as the given
/// widget in the scroll direction.
///
/// Specifying an [prototypeItem] is more efficient than letting the children
/// determine their own extent because the scrolling machinery can make use of
/// the foreknowledge of the children's extent to save work, for example when
/// the scroll position changes drastically.
///
/// See also:
///
/// * [SliverPrototypeExtentList], the sliver used internally when this
/// property is provided. It constrains its box children to have the same
/// extent as a prototype item along the main axis.
/// * The [itemExtent] property, which allows forcing the children's extent
/// to a given value.
/// {@endtemplate}
Widget? prototypeItem;
/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
double? cacheExtent;
/// The number of children that will contribute semantic information.
///
/// Some subtypes of [ScrollView] can infer this value automatically. For
/// example [ListView] will use the number of widgets in the child list,
/// while the [ListView.separated] constructor will use half that amount.
///
/// For [CustomScrollView] and other types which do not receive a builder
/// or list of widgets, the child count must be explicitly provided. If the
/// number is unknown or unbounded this should be left unset or set to null.
///
/// See also:
///
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
int? semanticChildCount;
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
DragStartBehavior? dragStartBehavior;
/// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
/// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
/// dismiss the keyboard automatically.
/// {@endtemplate}
ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
/// {@macro flutter.widgets.scrollable.restorationId}
String? restorationId;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
Clip? clipBehavior;
bool? shrinkWrap;
TIMUIKitHistoryMessageListConfig(
{this.key,
this.scrollDirection,
this.primary,
this.physics,
this.padding,
this.itemExtent,
this.prototypeItem,
this.cacheExtent,
this.semanticChildCount,
this.dragStartBehavior,
this.keyboardDismissBehavior,
this.restorationId,
this.shrinkWrap,
this.clipBehavior});
}
@@ -0,0 +1,620 @@
// ignore_for_file: non_constant_identifier_names, avoid_print
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:open_file/open_file.dart';
import 'package:provider/provider.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_role.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_status.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.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';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/forward_message_screen.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:path/path.dart' as path;
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_callback.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitMessageTooltip extends StatefulWidget {
/// tool tips panel configuration, long press message will show tool tips panel
final ToolTipsConfig? toolTipsConfig;
/// current message
final V2TimMessage message;
/// allow notifi user when send reply message
final bool allowAtUserWhenReply;
/// the callback for long press event, except myself avatar
final Function(String? userId, String? nickName)? onLongPressForOthersHeadPortrait;
final bool isUseMessageReaction;
/// direction
final SelectEmojiPanelPosition selectEmojiPanelPosition;
/// on add sticker reaction to a message
final ValueChanged<int> onSelectSticker;
/// on close tooltip area
final VoidCallback onCloseTooltip;
final TUIChatSeparateViewModel model;
final bool isShowMoreSticker;
final V2TimGroupMemberFullInfo? groupMemberInfo;
final bool iSUseDefaultHoverBar;
const TIMUIKitMessageTooltip(
{Key? key,
this.toolTipsConfig,
this.isUseMessageReaction = true,
required this.model,
required this.message,
required this.allowAtUserWhenReply,
this.onLongPressForOthersHeadPortrait,
required this.selectEmojiPanelPosition,
required this.onCloseTooltip,
required this.onSelectSticker,
this.isShowMoreSticker = false,
this.groupMemberInfo,
required this.iSUseDefaultHoverBar})
: super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitMessageTooltipState();
}
class TIMUIKitMessageTooltipState extends TIMUIKitState<TIMUIKitMessageTooltip> {
final TUIChatGlobalModel globalModal = serviceLocator<TUIChatGlobalModel>();
final TUISelfInfoViewModel selfInfoViewModel = serviceLocator<TUISelfInfoViewModel>();
bool isShowMoreSticker = false;
bool fileBeenDownloaded = false;
String filePath = "";
@override
void initState() {
super.initState();
hasFile();
isShowMoreSticker = widget.isShowMoreSticker;
}
hasFile() {
if (PlatformUtils().isMobile ||
(widget.message.fileElem == null && widget.message.imageElem == null && widget.message.videoElem == null)) {
fileBeenDownloaded = false;
return;
}
if (PlatformUtils().isWeb) {
fileBeenDownloaded = true;
return;
}
if (PlatformUtils().isDesktop) {
if (widget.message.fileElem != null) {
String savePath = TencentUtils.checkString(globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
"";
File f = File(savePath);
if (f.existsSync() && widget.message.msgID != null) {
filePath = savePath;
fileBeenDownloaded = true;
return;
}
} else if (widget.message.imageElem != null) {
if (TencentUtils.checkString(widget.message.imageElem!.imageList![0]!.localUrl) != null &&
File(widget.message.imageElem!.imageList![0]!.localUrl!).existsSync()) {
fileBeenDownloaded = true;
return;
}
} else if (widget.message.videoElem != null) {
if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) != null &&
File(widget.message.videoElem!.localVideoUrl!).existsSync()) {
fileBeenDownloaded = true;
return;
}
}
}
fileBeenDownloaded = false;
}
bool isRevocable(int timestamp, int upperTimeLimit) =>
((DateTime.now().millisecondsSinceEpoch / 1000).ceil() - timestamp < upperTimeLimit) &&
(widget.message.isSelf ?? true);
Widget ItemInkWell({
Widget? child,
GestureTapCallback? onTap,
}) {
return SizedBox(
width: 44,
child: InkWell(
onTap: onTap,
splashColor: Colors.white,
child: Container(
padding: const EdgeInsets.only(bottom: 6, top: 6),
child: child,
),
),
);
}
bool isAdminCanRecall() {
if (widget.model.chatConfig.isGroupAdminRecallEnabled) {
final selfMemberInfo = widget.groupMemberInfo ?? widget.model.selfMemberInfo;
final selfRole = selfMemberInfo?.role;
return selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_ADMIN ||
selfRole == GroupMemberRoleType.V2TIM_GROUP_MEMBER_ROLE_OWNER;
} else {
return false;
}
}
_buildLongPressTipItem(TUITheme theme, TUIChatSeparateViewModel model, V2TimMessage message) {
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
final isCanRevokeSelf = isRevocable(widget.message.timestamp!, model.chatConfig.upperRecallTime);
final shouldShowRevokeAction =
(isCanRevokeSelf || isAdminCanRecall()) && widget.message.status != MessageStatus.V2TIM_MSG_STATUS_SEND_FAIL;
final shouldShowReplyAction =
!(widget.message.customElem?.data != null && MessageUtils.isCallingData(widget.message.customElem!.data!));
final shouldShowForwardAction =
!(widget.message.customElem?.data != null && MessageUtils.isCallingData(widget.message.customElem!.data!));
final tooltipsConfig = widget.toolTipsConfig;
final messageCanCopy = widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT ||
(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);
final List<MessageToolTipItem> defaultTipsList = [
if (fileBeenDownloaded)
MessageToolTipItem(
label: TIM_t("打开"),
id: "open",
iconImageAsset: "images/open_in_new.png",
onClick: () => _onTap("open", model)),
if (fileBeenDownloaded && PlatformUtils().isDesktop)
MessageToolTipItem(
label: PlatformUtils().isMacOS ? TIM_t("在访达中打开") : TIM_t("查看文件夹"),
id: "finder",
iconImageAsset: "images/folder_open.png",
onClick: () => _onTap("finder", model)),
if (messageCanCopy)
MessageToolTipItem(
label: TIM_t("复制"),
id: "copyMessage",
iconImageAsset: "images/copy_message.png",
onClick: () => _onTap("copyMessage", model)),
if (shouldShowForwardAction && !model.isVoteMessage(widget.message))
MessageToolTipItem(
label: TIM_t("转发"),
id: "forwardMessage",
iconImageAsset: "images/forward_message.png",
onClick: () => _onTap("forwardMessage", model)),
if (shouldShowReplyAction)
MessageToolTipItem(
label: TIM_t((dynamicQuote ?? model.chatConfig.isAtWhenReply) ? "回复" : "引用"),
id: "replyMessage",
iconImageAsset: "images/reply_message.png",
onClick: () => _onTap("replyMessage", model)),
MessageToolTipItem(
label: TIM_t("多选"),
id: "multiSelect",
iconImageAsset: "images/multi_message.png",
onClick: () => _onTap("multiSelect", 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("撤回"),
id: "revoke",
iconImageAsset: "images/revoke_message.png",
onClick: () => _onTap("revoke", model)),
];
final defaultTipsIds = defaultTipsList.map((e) => e.id);
List<MessageToolTipItem> defaultFormattedTipsList = defaultTipsList;
if (tooltipsConfig != null) {
defaultFormattedTipsList = defaultTipsList.where((element) {
final type = element.id;
if (type == "copyMessage") {
return tooltipsConfig.showCopyMessage;
}
if (type == "forwardMessage") {
return tooltipsConfig.showForwardMessage && !(isDesktopScreen && widget.iSUseDefaultHoverBar);
}
if (type == "replyMessage") {
return tooltipsConfig.showReplyMessage && !(isDesktopScreen && widget.iSUseDefaultHoverBar);
}
if (type == "delete") {
return (!PlatformUtils().isWeb) && tooltipsConfig.showDeleteMessage;
}
if (type == "multiSelect") {
return tooltipsConfig.showMultipleChoiceMessage;
}
if (type == "revoke") {
return tooltipsConfig.showRecallMessage;
}
if (type == "translate") {
return tooltipsConfig.showTranslation && widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT;
}
return true;
}).toList();
}
final List<MessageToolTipItem>? customList = widget.toolTipsConfig?.additionalMessageToolTips != null
? (widget.toolTipsConfig?.additionalMessageToolTips!(message, widget.onCloseTooltip))
: [];
List<MessageToolTipItem> formattedTipsList = [
...defaultFormattedTipsList,
...?customList,
];
List<dynamic> widgetList = [];
if (isDesktopScreen) {
widgetList = formattedTipsList
.map(
(item) => Material(
color: Colors.white,
child: InkWell(
onTap: () {
item.onClick();
},
child: Container(
padding: const EdgeInsets.all(6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
item.iconImageAsset,
package: defaultTipsIds.contains(item.id) ? 'tencent_cloud_chat_uikit' : null,
width: 20,
height: 20,
),
const SizedBox(
height: 4,
width: 8,
),
Text(
item.label,
style: TextStyle(
decoration: TextDecoration.none,
color: theme.darkTextColor,
fontSize: 12,
),
)
],
),
),
),
),
)
.toList();
} else {
widgetList = formattedTipsList
.map(
(item) => Material(
color: Colors.white,
child: ItemInkWell(
onTap: () {
item.onClick();
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
item.iconImageAsset,
package: defaultTipsIds.contains(item.id) ? 'tencent_cloud_chat_uikit' : null,
width: 20,
height: 20,
),
const SizedBox(
height: 4,
width: 60,
),
Text(
item.label,
style: TextStyle(
decoration: TextDecoration.none,
color: theme.darkTextColor,
fontSize: 10,
),
)
],
),
),
),
)
.toList();
}
if (widgetList.isEmpty && widget.isUseMessageReaction == false) {
widget.onCloseTooltip();
}
return widgetList;
}
_onOpenDesktop(String path) {
try {
if (PlatformUtils().isDesktop && !PlatformUtils().isWindows) {
launchUrl(Uri.file(path));
} else {
OpenFile.open(path);
}
// ignore: empty_catches
} catch (e) {}
}
_onTap(String operation, TUIChatSeparateViewModel model) async {
final messageItem = widget.message;
final msgID = messageItem.msgID as String;
switch (operation) {
case "open":
if (widget.message.fileElem != null) {
_onOpenDesktop(TencentUtils.checkString(globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
"");
} else if (widget.message.imageElem != null) {
_onOpenDesktop(TencentUtils.checkString(widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
"");
} else if (widget.message.videoElem != null) {
_onOpenDesktop(TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) ??
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
"");
}
break;
case "finder":
String savePath = "";
if (widget.message.fileElem != null) {
savePath = (TencentUtils.checkString(globalModal.getFileMessageLocation(widget.message.msgID)) ??
TencentUtils.checkString(widget.message.fileElem!.localUrl) ??
widget.message.fileElem?.path ??
"");
} else if (widget.message.imageElem != null) {
savePath = (TencentUtils.checkString(widget.message.imageElem!.imageList?[0]?.localUrl) ??
TencentUtils.checkString(widget.message.imageElem?.path) ??
"");
} else if (widget.message.videoElem != null) {
savePath = (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) ??
TencentUtils.checkString(widget.message.videoElem?.videoPath) ??
"");
}
final String fileDir = path.dirname(savePath);
_onOpenDesktop(fileDir);
break;
case "delete":
model.deleteMsg(msgID, webMessageInstance: messageItem.messageFromWeb);
break;
case "revoke":
model.revokeMsg(msgID, !isRevocable(widget.message.timestamp!, model.chatConfig.upperRecallTime),
messageItem.messageFromWeb);
break;
case 'translate':
model.translateText(widget.message);
break;
case "multiSelect":
model.updateMultiSelectStatus(true);
model.setMessageItemChecked(widget.message, true);
break;
case "forwardMessage":
model.setMessageItemChecked(widget.message, true);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ForwardMessageScreen(
conversationType: ConvType.c2c,
model: model,
)));
break;
case "copyMessage":
if (widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) {
try {
await Clipboard.setData(ClipboardData(text: widget.message.textElem?.text ?? ""));
onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("已复制"), infoCode: 6660408));
// ignore: empty_catches
} catch (e) {}
}
break;
case "replyMessage":
model.repliedMessage = widget.message;
final dynamicQuote = model.chatConfig.isAtWhenReplyDynamic?.call(widget.message);
final isSelf = widget.message.isSelf ?? true;
final isGroup = TencentUtils.checkString(widget.message.groupID) != null;
final isAtWhenReply = !isSelf &&
isGroup &&
(dynamicQuote ?? widget.allowAtUserWhenReply) &&
widget.onLongPressForOthersHeadPortrait != null;
/// If replying to a self message, do not add a at tag, only requestFocus.
widget.onLongPressForOthersHeadPortrait!(
!isAtWhenReply ? null : widget.message.sender, !isAtWhenReply ? null : widget.message.nickName);
break;
default:
onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("暂未实现"), infoCode: 6660409));
}
widget.onCloseTooltip();
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final isDesktopScreen = TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: widget.model),
],
builder: (BuildContext context, Widget? w) {
final TUIChatSeparateViewModel model = Provider.of<TUIChatSeparateViewModel>(context);
final bool haveExtraTipsConfig =
widget.toolTipsConfig != null && widget.toolTipsConfig?.additionalItemBuilder != null;
Widget? extraTipsActionItem = haveExtraTipsConfig
? widget.toolTipsConfig!.additionalItemBuilder!(widget.message, widget.onCloseTooltip, null, context)
: null;
final message = widget.message;
return Container(
decoration: isDesktopScreen
? BoxDecoration(
boxShadow: const [
BoxShadow(
color: Color(0xCCbebebe),
offset: Offset(2, 2),
blurRadius: 10,
spreadRadius: 1,
),
],
border: Border.all(
width: 1,
color: hexToColor("dee0e3"),
),
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(10)),
)
: null,
color: isDesktopScreen ? null : Colors.white,
padding: EdgeInsets.symmetric(horizontal: 8, vertical: isDesktopScreen ? 8 : 4),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: min(MediaQuery.of(context).size.width * 0.75, 350),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if ((!isDesktopScreen || widget.isShowMoreSticker) &&
widget.isUseMessageReaction &&
widget.selectEmojiPanelPosition == SelectEmojiPanelPosition.up)
TIMUIKitMessageReactionEmojiSelectPanel(
isShowMoreSticker: isShowMoreSticker,
onSelect: (int value) => widget.onSelectSticker(value),
onClickShowMore: (bool value) {
setState(() {
isShowMoreSticker = value;
});
},
),
if (!isDesktopScreen &&
widget.isUseMessageReaction &&
widget.selectEmojiPanelPosition == SelectEmojiPanelPosition.up &&
isShowMoreSticker == false)
Container(
margin: const EdgeInsets.symmetric(vertical: 6),
child: const Divider(
thickness: 1,
indent: 0,
// endIndent: 10,
color: Colors.black12)),
if (isShowMoreSticker == false)
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!isDesktopScreen && widget.isUseMessageReaction)
Expanded(
child: Wrap(
direction: Axis.horizontal,
alignment: TUIKitScreenUtils.getFormFactor(context) == DeviceType.Mobile
? WrapAlignment.start
: WrapAlignment.start,
spacing: 12,
runSpacing: 8,
children: [
..._buildLongPressTipItem(theme, model, message),
if (extraTipsActionItem != null) extraTipsActionItem
],
)),
if (!isDesktopScreen && !widget.isUseMessageReaction)
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: min(MediaQuery.of(context).size.width * 0.75, 350),
),
child: Wrap(
direction: Axis.horizontal,
alignment: TUIKitScreenUtils.getFormFactor(context) == DeviceType.Mobile
? WrapAlignment.spaceBetween
: WrapAlignment.start,
spacing: 4,
runSpacing: 8,
children: [
..._buildLongPressTipItem(theme, model, message),
if (extraTipsActionItem != null) extraTipsActionItem
],
),
),
if (isDesktopScreen)
Table(columnWidths: const <int, TableColumnWidth>{
0: IntrinsicColumnWidth(),
}, children: <TableRow>[
..._buildLongPressTipItem(theme, model, message).map((e) => TableRow(children: <Widget>[e]))
])
],
),
if (!isDesktopScreen &&
widget.isUseMessageReaction &&
widget.selectEmojiPanelPosition == SelectEmojiPanelPosition.down &&
isShowMoreSticker == false)
Container(
margin: const EdgeInsets.symmetric(vertical: 6),
child: const Divider(
thickness: 1,
indent: 0,
// endIndent: 10,
color: Colors.black12)),
if ((!isDesktopScreen || widget.isShowMoreSticker) &&
widget.isUseMessageReaction &&
widget.selectEmojiPanelPosition == SelectEmojiPanelPosition.down)
TIMUIKitMessageReactionEmojiSelectPanel(
isShowMoreSticker: isShowMoreSticker,
onSelect: (int value) => widget.onSelectSticker(value),
onClickShowMore: (bool value) {
setState(() {
isShowMoreSticker = value;
});
},
),
],
),
));
},
);
}
}
@@ -0,0 +1,203 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_at_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_at_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/ui/constants/history_message_constant.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/TIMUIKitTongue/tim_uikit_chat_history_message_list_tongue.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_config.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list_item.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKItMessageList/tim_uikit_chat_history_message_list.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/tim_uikit_chat_config.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tim_ui_kit_sticker_plugin/utils/tim_custom_face_data.dart';
enum LoadingPlace {
none,
top,
bottom,
}
class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
final Widget Function(BuildContext, V2TimMessage?)? itemBuilder;
final AutoScrollController? scrollController;
final String conversationID;
final Function(String? userId, String? nickName)? onLongPressForOthersHeadPortrait;
final List<V2TimGroupAtInfo?>? groupAtInfoList;
final V2TimMessage? initFindingMsg;
/// message item builder, works for customize all message types and row layout.
final MessageItemBuilder? messageItemBuilder;
/// The controller for text field.
final TIMUIKitInputTextFieldController? textFieldController;
/// the builder for avatar
final Widget Function(BuildContext context, V2TimMessage message)? userAvatarBuilder;
/// the builder for tongue
final TongueItemBuilder? tongueItemBuilder;
final Widget? Function(V2TimMessage message, Function() closeTooltip, [Key? key, BuildContext? context])?
extraTipsActionItemBuilder;
/// conversation type
final ConvType conversationType;
/// Avatar and name in message reaction tap callback.
final void Function(String userID, TapDownDetails tapDetails)? onTapAvatar;
/// Avatar and name in message reaction secondary tap callback.
final void Function(String userID, TapDownDetails tapDetails)? onSecondaryTapAvatar;
@Deprecated(
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
final bool showNickName;
final TIMUIKitHistoryMessageListConfig? mainHistoryListConfig;
/// tool tips panel configuration, long press message will show tool tips panel
final ToolTipsConfig? toolTipsConfig;
final List<CustomEmojiFaceData> customEmojiStickerList;
final bool isAllowScroll;
final V2TimConversation conversation;
final V2TimGroupMemberFullInfo? groupMemberInfo;
/// This parameter accepts a custom widget to be displayed when the mouse hovers over a message,
/// replacing the default message hover action bar.
/// Applicable only on desktop platforms.
/// If provided, the default message action functionality will appear in the right-click context menu instead.
final Widget? Function(V2TimMessage message)? customMessageHoverBarOnDesktop;
const TIMUIKitHistoryMessageListContainer({
Key? key,
this.itemBuilder,
this.scrollController,
required this.conversationID,
required this.conversationType,
this.userAvatarBuilder,
this.onLongPressForOthersHeadPortrait,
this.groupAtInfoList,
this.messageItemBuilder,
this.tongueItemBuilder,
this.extraTipsActionItemBuilder,
this.isAllowScroll = true,
this.onTapAvatar,
@Deprecated(
"Nickname will not show in one-to-one chat, if you tend to control it in group chat, please use `isShowSelfNameInGroup` and `isShowOthersNameInGroup` from `config: TIMUIKitChatConfig` instead")
this.showNickName = true,
this.initFindingMsg,
this.mainHistoryListConfig,
this.toolTipsConfig,
this.customEmojiStickerList = const [],
this.textFieldController,
required this.conversation,
this.onSecondaryTapAvatar,
this.groupMemberInfo,
this.customMessageHoverBarOnDesktop,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitHistoryMessageListContainerState();
}
class _TIMUIKitHistoryMessageListContainerState extends TIMUIKitState<TIMUIKitHistoryMessageListContainer> {
late TIMUIKitHistoryMessageListController _historyMessageListController;
List<V2TimMessage?> historyMessageList = [];
Future<bool> requestForData(String? lastMsgID, LoadDirection direction, TUIChatSeparateViewModel model,
[int? count, int? lastSeq]) async {
if ((direction == LoadDirection.previous) || (direction == LoadDirection.latest && model.haveMoreLatestData)) {
return await model.loadChatRecord(
direction: direction,
count: count ?? (kIsWeb ? 15 : HistoryMessageDartConstant.getCount),
lastMsgID: lastMsgID,
lastMsgSeq: lastSeq ?? -1,
);
} else {
return false;
}
}
Widget Function(BuildContext, V2TimMessage)? _getTopRowBuilder(TUIChatSeparateViewModel model) {
if (widget.messageItemBuilder?.messageNickNameBuilder != null) {
return (BuildContext context, V2TimMessage message) {
return widget.messageItemBuilder!.messageNickNameBuilder!(context, message, model);
};
}
return null;
}
@override
void initState() {
super.initState();
_historyMessageListController = TIMUIKitHistoryMessageListController(scrollController: widget.scrollController);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final chatConfig = Provider.of<TIMUIKitChatConfig>(context);
final TUIChatSeparateViewModel model = Provider.of<TUIChatSeparateViewModel>(context, listen: false);
return TIMUIKitHistoryMessageListSelector(
conversationID: model.conversationID,
builder: (context, messageList, child) {
return TIMUIKitHistoryMessageList(
conversation: widget.conversation,
model: model,
isAllowScroll: widget.isAllowScroll,
controller: _historyMessageListController,
groupAtInfoList: widget.groupAtInfoList,
mainHistoryListConfig: widget.mainHistoryListConfig,
itemBuilder: (context, message) {
return TIMUIKitHistoryMessageListItem(
customMessageHoverBarOnDesktop: widget.customMessageHoverBarOnDesktop,
groupMemberInfo: widget.groupMemberInfo,
textFieldController: widget.textFieldController,
userAvatarBuilder: widget.userAvatarBuilder,
customEmojiStickerList: widget.customEmojiStickerList,
topRowBuilder: _getTopRowBuilder(model),
onScrollToIndex: _historyMessageListController.scrollToIndex,
onScrollToIndexBegin: _historyMessageListController.scrollToIndexBegin,
toolTipsConfig:
widget.toolTipsConfig ?? ToolTipsConfig(additionalItemBuilder: widget.extraTipsActionItemBuilder),
message: message!,
showAvatar: chatConfig.isShowAvatar,
onSecondaryTapForOthersPortrait: widget.onSecondaryTapAvatar,
onTapForOthersPortrait: widget.onTapAvatar,
messageItemBuilder: widget.messageItemBuilder,
onLongPressForOthersHeadPortrait: widget.onLongPressForOthersHeadPortrait,
allowAtUserWhenReply: chatConfig.isAtWhenReply,
allowAvatarTap: chatConfig.isAllowClickAvatar,
allowLongPress: chatConfig.isAllowLongPressMessage,
isUseMessageReaction: chatConfig.isUseMessageReaction);
},
tongueItemBuilder: widget.tongueItemBuilder,
initFindingMsg: widget.initFindingMsg,
messageList: messageList,
onLoadMore: (String? a, LoadDirection direction, [int? b, int? lastSeq]) async {
return await requestForData(a, direction, model, b, lastSeq);
},
);
},
);
}
}
@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message_receipt.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/core/tim_uikit_wide_modal_operation_key.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/message_read_receipt.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/wide_popup.dart';
class TIMUIKitMessageReadReceipt extends TIMUIKitStatelessWidget {
final V2TimMessage messageItem;
final void Function(String, TapDownDetails tapDetails)? onTapAvatar;
TIMUIKitMessageReadReceipt({Key? key, this.onTapAvatar, required this.messageItem}) : super(key: key);
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
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 (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: () {
if ((value?.readCount ?? 0) > 0) {
if (isDesktopScreen) {
TUIKitWidePopup.showPopupWindow(
operationKey: TUIKitWideModalOperationKey.messageReadDetails,
context: context,
width: MediaQuery.of(context).size.width * 0.5,
height: MediaQuery.of(context).size.height * 0.8,
title: TIM_t("消息详情"),
child: (onClose) => MessageReadReceipt(
model: model,
onTapAvatar: onTapAvatar,
messageItem: messageItem,
unreadCount: value?.unreadCount ?? 0,
readCount: value?.readCount ?? 0));
} else {
if (value?.unreadCount == 0) {
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MessageReadReceipt(
model: model,
onTapAvatar: onTapAvatar,
messageItem: messageItem,
unreadCount: value?.unreadCount ?? 0,
readCount: value?.readCount ?? 0)));
}
}
},
child: Container(
padding: EdgeInsets.only(bottom: 3, right: 6, left: 6, top: isDesktopScreen ? 2 : 6),
child: ((value?.unreadCount ?? 0) == 0 && (value?.readCount ?? 0) > 0)
? Icon(
Icons.check_circle_outline,
size: 18,
color: theme.weakTextColor,
)
: Container(
width: 14,
height: 14,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 1.3,
color: (value?.readCount ?? 0) > 0 ? theme.primaryColor! : theme.weakTextColor!)),
child: (value?.readCount ?? 0) > 0
? Text(
'${value?.readCount ?? 0}',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 8, color: theme.primaryColor),
)
: null,
),
),
);
}
},
selector: (c, model) {
return model.getMessageReadReceipt(messageItem.msgID ?? "");
},
);
}
}
@@ -0,0 +1,13 @@
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_message.dart';
class TIMUIKitChatUtils {
static String? getMessageIDWithinIndex(List<V2TimMessage?> messageList, int index) {
if (messageList[index]!.elemType == 11) {
if (index > 0) {
return getMessageIDWithinIndex(messageList, index - 1);
}
}
return messageList[index]!.msgID;
}
}
@@ -0,0 +1,272 @@
// ignore_for_file: deprecated_member_use
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimFriendshipListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimGroupListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_change_info_type.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_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';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/utils/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitAppBar/tim_uikit_appbar_title.dart';
import 'package:tuple/tuple.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitAppBar extends StatefulWidget implements PreferredSizeWidget {
/// Appbar config
final AppBar? config;
/// Allow show conversation total unread count
final bool showTotalUnReadCount;
/// Conversation id
final String conversationID;
/// conversation name
final String conversationShowName;
/// If allow update the conversation show name automatically.
final bool isConversationShowNameFixed;
final bool showC2cMessageEditStatus;
final GestureTapDownCallback? onClickTitle;
const TIMUIKitAppBar({
Key? key,
this.config,
this.isConversationShowNameFixed = false,
this.showTotalUnReadCount = true,
this.conversationID = "",
this.conversationShowName = "",
this.showC2cMessageEditStatus = true,
this.onClickTitle,
}) : super(key: key);
@override
Size get preferredSize =>
config?.preferredSize ?? const Size.fromHeight(56.0);
@override
State<StatefulWidget> createState() => _TIMUIKitAppBarState();
}
class _TIMUIKitAppBarState extends TIMUIKitState<TIMUIKitAppBar> {
final FriendshipServices _friendshipServices =
serviceLocator<FriendshipServices>();
final GroupServices _groupServices = serviceLocator<GroupServices>();
V2TimFriendshipListener? _friendshipListener;
V2TimGroupListener? _groupListener;
String _conversationShowName = "";
_addConversationShowNameListener() {
_friendshipListener = V2TimFriendshipListener(
onFriendInfoChanged: (infoList) {
try {
final changedInfo = infoList.firstWhere(
(element) => element.userID == widget.conversationID,
);
if (changedInfo.friendRemark != null &&
changedInfo.friendRemark!.isNotEmpty) {
_conversationShowName = changedInfo.friendRemark!;
setState(() {});
} else {
_conversationShowName = (changedInfo.userProfile?.nickName ??
changedInfo.userProfile?.userID) ??
"";
}
// ignore: empty_catches
} catch (e) {}
},
);
if (_friendshipListener != null) {
_friendshipServices.addFriendListener(listener: _friendshipListener!);
}
}
_addGroupListener() {
_groupListener = V2TimGroupListener(
onGroupInfoChanged: (groupID, changeInfos) {
try {
if (groupID == widget.conversationID) {
final groupNameChangeInfo = changeInfos.firstWhere((element) =>
element.type ==
GroupChangeInfoType.V2TIM_GROUP_INFO_CHANGE_TYPE_NAME);
if (groupNameChangeInfo.value != null) {
_conversationShowName = groupNameChangeInfo.value!;
setState(() {});
}
}
// ignore: empty_catches
} catch (e) {}
},
);
if (_groupListener != null) {
_groupServices.addGroupListener(listener: _groupListener!);
}
}
String _getTotalUnReadCount(int unreadCount) {
return unreadCount < 99 ? unreadCount.toString() : "99";
}
@override
void initState() {
super.initState();
_conversationShowName = widget.conversationShowName;
if (!widget.isConversationShowNameFixed) {
_addConversationShowNameListener();
_addGroupListener();
}
}
@override
void dispose() {
super.dispose();
if (!widget.isConversationShowNameFixed) {
_groupServices.removeGroupListener(listener: _groupListener);
_friendshipServices.removeFriendListener(listener: _friendshipListener);
}
}
@override
void didUpdateWidget(TIMUIKitAppBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.conversationShowName != widget.conversationShowName) {
if (widget.conversationShowName.isNotEmpty) {
setState(() {
_conversationShowName = widget.conversationShowName;
});
} else {
updateTitleFuture();
}
}
}
void updateTitleFuture() async {
try {
final res = await _friendshipServices
.getFriendsInfo(userIDList: [widget.conversationID]);
if (res != null && res.isNotEmpty && res.first.resultCode == 0) {
setState(() {
_conversationShowName = res.first.friendInfo?.userProfile?.nickName ??
res.first.friendInfo?.userProfile?.userID ??
"";
});
}
// ignore: empty_catches
} catch (e) {}
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final setAppbar = widget.config;
final TUIChatSeparateViewModel chatVM =
Provider.of<TUIChatSeparateViewModel>(context);
final isDesktopScreen =
TUIKitScreenUtils.getFormFactor(context) == DeviceType.Desktop;
return AppBar(
backgroundColor: setAppbar?.backgroundColor ??
theme.chatHeaderBgColor ??
theme.appbarBgColor ??
theme.primaryColor,
actionsIconTheme: setAppbar?.actionsIconTheme,
foregroundColor: setAppbar?.foregroundColor,
elevation: setAppbar?.elevation ?? (isDesktopScreen ? 0 : 1),
bottom: setAppbar?.bottom,
bottomOpacity: setAppbar?.bottomOpacity ?? 1.0,
titleSpacing: setAppbar?.titleSpacing,
automaticallyImplyLeading: setAppbar?.automaticallyImplyLeading ?? false,
shadowColor: setAppbar?.shadowColor ?? theme.weakDividerColor,
excludeHeaderSemantics: setAppbar?.excludeHeaderSemantics ?? false,
toolbarHeight: setAppbar?.toolbarHeight,
titleTextStyle: setAppbar?.titleTextStyle,
toolbarOpacity: setAppbar?.toolbarOpacity ?? 1.0,
toolbarTextStyle: setAppbar?.toolbarTextStyle,
// textTheme: setAppbar?.textTheme,
iconTheme: setAppbar?.iconTheme ??
const IconThemeData(
color: Colors.white,
),
title: TIMUIKitAppBarTitle(
title: setAppbar?.title,
onClick: widget.onClickTitle,
textStyle: TextStyle(
color: theme.appbarTextColor ?? hexToColor("010000"), fontSize: 16),
conversationShowName: _conversationShowName,
showC2cMessageEditStatus: widget.showC2cMessageEditStatus,
fromUser: widget.conversationID,
),
centerTitle: setAppbar?.centerTitle ?? (isDesktopScreen ? false : true),
leadingWidth: setAppbar?.leadingWidth ?? (isDesktopScreen ? 8 : 70),
leading: Selector<TUIChatGlobalModel, Tuple2<bool, int>>(
builder: (context, data, _) {
final isMultiSelect = data.item1;
final unReadCount = data.item2;
return (!isDesktopScreen && isMultiSelect)
? TextButton(
onPressed: () {
chatVM.updateMultiSelectStatus(false);
},
child: Text(
TIM_t('取消'),
style: TextStyle(
color: theme.appbarTextColor ?? hexToColor("010000"),
fontSize: 16,
),
),
)
: setAppbar?.leading ??
(isDesktopScreen
? Container()
: Row(
children: [
IconButton(
padding: const EdgeInsets.only(left: 16),
constraints: const BoxConstraints(),
icon: Icon(
Icons.arrow_back_ios,
color: hexToColor("010000"),
size: 17,
),
onPressed: () async {
chatVM.repliedMessage = null;
Navigator.pop(context);
},
),
if (widget.showTotalUnReadCount &&
unReadCount > 0)
Container(
width: 22,
height: 22,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: theme.cautionColor,
),
child:
Text(_getTotalUnReadCount(unReadCount)),
),
],
));
},
shouldRebuild: (prev, next) =>
prev.item1 != next.item1 || prev.item2 != next.item2,
selector: (_, model) =>
Tuple2(chatVM.isMultiSelect, model.totalUnReadCount)),
actions: setAppbar?.actions,
);
}
}
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tencent_chat_i18n_tool/tencent_chat_i18n_tool.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart';
class TIMUIKitAppBarTitle extends StatelessWidget {
final Widget? title;
final String conversationShowName;
final bool showC2cMessageEditStatus;
final String fromUser;
final GestureTapDownCallback? onClick;
final TextStyle? textStyle;
const TIMUIKitAppBarTitle(
{Key? key,
this.title,
this.textStyle,
required this.conversationShowName,
required this.showC2cMessageEditStatus,
required this.fromUser, this.onClick})
: super(key: key);
Widget titleText(String text){
return InkWell(
onTapDown: onClick,
child: Text(
text,
style: textStyle ??
const TextStyle(
color: Colors.white,
fontSize: 17,
),
),
);
}
// String conversationShowName;
@override
Widget build(BuildContext context) {
int status = Provider.of<TUIChatGlobalModel>(context, listen: true)
.getC2cMessageEditStatus(fromUser);
if (status == 0) {
if (title != null) {
return title!;
}
return titleText(conversationShowName,);
} else {
if (showC2cMessageEditStatus) {
return titleText(
TIM_t("对方正在输入中..."),);
} else {
if (title != null) {
return title!;
}
return titleText(
conversationShowName,);
}
}
}
}
@@ -0,0 +1,36 @@
List<Map<String, Object>> messageReactionEmojiData = [
{"name": "red heart", "unicode": 10084},
{"name": "OK", "unicode": 128076},
{"name": "thumbs up", "unicode": 128077},
{"name": "flexed biceps", "unicode": 128170},
{"name": "waving hand", "unicode": 128079},
{"name": "handshake", "unicode": 129309},
{"name": "folded hands", "unicode": 128591},
{"name": "raised fist", "unicode": 9994},
{"name": "thumbs down", "unicode": 128078},
{"name": "smiling face with sunglasses", "unicode": 128526},
{"name": "GRINNING FACE WITH SMILING EYES", "unicode": 128513},
{"name": "SMILING FACE WITH SMILING EYES", "unicode": 128522},
{"name": "smiling face with hearts", "unicode": 129392},
{"name": "SMILING FACE WITH HEART-SHAPED EYES", "unicode": 128525},
{"name": "FACE WITH TEARS OF JOY", "unicode": 128514},
{"name": "flushed face", "unicode": 128563},
{"name": "SMILING FACE WITH OPEN MOUTH AND COLD SWEAT", "unicode": 128517},
{"name": "FACE WITH COLD SWEAT", "unicode": 128531},
{"name": "loudly crying face", "unicode": 128557},
{"name": "CONFOUNDED FACE", "unicode": 128534},
{"name": "KISSING FACE WITH CLOSED EYES", "unicode": 128538},
{
"name": "FACE WITH STUCK-OUT TONGUE AND TIGHTLY-CLOSED EYES",
"unicode": 128541
},
{"name": "DISAPPOINTED FACE", "unicode": 128542},
{"name": "ANGRY FACE", "unicode": 128544},
{"name": "POUTING FACE", "unicode": 128545},
{"name": "CRYING FACE", "unicode": 128546},
{"name": "FACE WITH LOOK OF TRIUMPH", "unicode": 128548},
{"name": "FEARFUL FACE", "unicode": 128552},
{"name": "SLEEPY FACE", "unicode": 128554},
{"name": "FACE WITH NO GOOD GESTURE", "unicode": 128581},
{"name": "FACE WITH OK GESTURE", "unicode": 128582},
];
@@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart'
if (dart.library.html) 'package:tencent_cloud_chat_sdk/web/compatible_models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_self_info_view_model.dart';
import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/avatar.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
class TIMUIKitMessageReactionDetail extends StatefulWidget {
/// the index of the current emoji sticker
final int currentStickerIndex;
/// the list of member
final List<V2TimGroupMemberFullInfo?>? memberList;
/// message reaction map
final Map<String, dynamic> messageReaction;
/// the sticker list from message reaction
final List<int> stickerList;
final Function(String userID, TapDownDetails tapDetails)? onTapAvatar;
const TIMUIKitMessageReactionDetail(
{required this.currentStickerIndex,
this.memberList,
required this.messageReaction,
Key? key,
required this.stickerList,
this.onTapAvatar})
: super(key: key);
@override
State<StatefulWidget> createState() => TIMUIKitMessageReactionDetailState();
}
class TIMUIKitMessageReactionDetailState extends TIMUIKitState<TIMUIKitMessageReactionDetail>
with TickerProviderStateMixin {
final TUISelfInfoViewModel selfInfoModel = serviceLocator<TUISelfInfoViewModel>();
Widget getUserItem(String userID, TUITheme theme, Function(String userID, TapDownDetails tapDetails)? onTapAvatar) {
V2TimGroupMemberFullInfo? memberInfo;
String showName = userID;
try {
memberInfo = widget.memberList?.firstWhere((element) => element?.userID == userID);
if (memberInfo != null) {
if (memberInfo.friendRemark != null && memberInfo.friendRemark!.isNotEmpty) {
showName = memberInfo.friendRemark!;
} else if (memberInfo.nameCard != null && memberInfo.nameCard!.isNotEmpty) {
showName = memberInfo.nameCard!;
} else if (memberInfo.nickName != null && memberInfo.nickName!.isNotEmpty) {
showName = memberInfo.nickName!;
} else {
showName = memberInfo.userID;
}
}
} catch (e) {
// e
}
return InkWell(
onTapDown: (tapDetails) {
if (onTapAvatar != null) {
if (userID != selfInfoModel.loginInfo?.userID) {
onTapAvatar(userID, tapDetails);
}
}
},
child: Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: theme.weakDividerColor ?? CommonColor.weakDividerColor))),
child: Row(
children: [
Container(
margin: const EdgeInsets.only(right: 12),
child: SizedBox(
height: 40,
width: 40,
child: (memberInfo?.faceUrl != null)
? Avatar(faceUrl: memberInfo!.faceUrl!, showName: userID)
: Container(),
),
),
Expanded(
child: Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(top: 20, bottom: 20, right: 28),
child: Text(
showName,
style: TextStyle(color: theme.black, fontSize: 18),
),
)),
],
),
),
);
}
Widget stickerItem(int sticker, int length) {
return Container(
margin: const EdgeInsets.only(top: 12, bottom: 12),
padding: const EdgeInsets.only(left: 10, right: 10, top: 2, bottom: 1),
decoration: const BoxDecoration(
color: Color(0x198a8a8a),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.only(bottom: 3),
child: Text(
String.fromCharCode(sticker),
style: const TextStyle(
fontSize: 16,
),
),
),
Container(
margin: const EdgeInsets.only(left: 6),
child: Text(length.toString()),
),
],
),
);
}
Widget getStickerNameList(
int sticker, TUITheme theme, Function(String userID, TapDownDetails tapDetails)? onTapAvatar) {
final nameList = widget.messageReaction[sticker.toString()];
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [...nameList.map((e) => getUserItem(e, theme, onTapAvatar))],
),
);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
return DefaultTabController(
initialIndex: widget.currentStickerIndex,
length: widget.stickerList.length,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(bottom: 4),
child: TabBar(
isScrollable: true,
labelColor: theme.primaryColor,
labelStyle: const TextStyle(fontWeight: FontWeight.bold),
unselectedLabelColor: hexToColor("62626b"),
unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.normal),
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: theme.primaryColor ?? hexToColor("62626b"),
tabs: [
...widget.stickerList.map((element) {
return stickerItem(element, widget.messageReaction[element.toString()].length);
})
],
),
),
Expanded(
child: TabBarView(
children: widget.stickerList
.map((int sticker) => getStickerNameList(sticker, theme, widget.onTapAvatar))
.toList()))
],
),
));
}
}
@@ -0,0 +1,93 @@
import 'package:flutter/material.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/screen_utils.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/TIMUIKitMessageReaction/message_reaction_emoji.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_emoji_panel.dart'
as emoji;
import 'package:tencent_cloud_chat_uikit/ui/widgets/emoji.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/extended_wrap/extended_wrap.dart';
import 'package:tencent_cloud_chat_uikit/theme/color.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
enum SelectEmojiPanelPosition { up, down }
class TIMUIKitMessageReactionEmojiSelectPanel extends StatefulWidget {
final ValueChanged<int> onSelect;
final bool isShowMoreSticker;
final ValueChanged<bool> onClickShowMore;
const TIMUIKitMessageReactionEmojiSelectPanel(
{Key? key,
required this.onSelect,
required this.isShowMoreSticker,
required this.onClickShowMore})
: super(key: key);
@override
State<StatefulWidget> createState() =>
TIMUIKitMessageReactionEmojiSelectPanelState();
}
class TIMUIKitMessageReactionEmojiSelectPanelState
extends TIMUIKitState<TIMUIKitMessageReactionEmojiSelectPanel> {
bool isShowMore = false;
_buildSimplePanel(TUITheme theme) {
final List<Map<String, Object>> emojiData = messageReactionEmojiData;
final isDesktopScreen = TUIKitScreenUtils.getFormFactor() == DeviceType.Desktop;
return Material(
color: Colors.white,
child: ExtendedWrap(
maxLines: widget.isShowMoreSticker ? 5 : 1,
spacing: 18,
crossAxisAlignment: WrapCrossAlignment.center,
runSpacing: 24,
children: [
if(!isDesktopScreen)
GestureDetector(
onTap: () {
widget.onClickShowMore(!widget.isShowMoreSticker);
},
child: SizedBox(
height: 34,
child: Icon(
widget.isShowMoreSticker
? Icons.cancel_outlined
: Icons.add_circle_outline_outlined,
color: hexToColor("444444"),
size: 26),
),
),
...emojiData.map(
(e) {
var item = Emoji.fromJson(e);
return SizedBox(
// width: 50,
child: InkWell(
splashColor: Colors.white,
onTap: () {
widget.onSelect(item.unicode);
},
child: emoji.EmojiItem(
name: item.name,
unicode: item.unicode,
),
),
);
},
).toList()
],
),
);
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
return Container(
child: _buildSimplePanel(theme),
);
}
}

Some files were not shown because too many files have changed in this diff Show More