nim_core_v2 10.9.5+1
nim_core_v2: ^10.9.5+1 copied to clipboard
A Flutter plugin for NetEase IM SDK on Android, iOS and Windows.
// Copyright (c) 2022 NetEase, Inc. All rights reserved.
// Use of this source code is governed by a MIT license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:universal_io/io.dart';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:nim_core_v2/nim_core.dart';
import 'package:path_provider/path_provider.dart';
import 'package:universal_html/html.dart' as html;
import 'package:http/http.dart' as http;
import 'package:yunxin_alog/yunxin_alog.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// For Publish Use
static const appKey = 'Your_App_Key';
static const account = 'Account_ID';
static const token = 'Account_Token';
static const friendAccount = 'Friend_Account_ID';
static const chatroomId1 = '123456789';
static const teamId = '35079835747';
V2NIMChatroomClient? chatroomClient;
final subsriptions = <StreamSubscription>[];
final chatroomSubsriptions = <StreamSubscription>[];
Uint8List? _deviceToken;
void updateAPNsToken() {
if (Platform.isIOS && _deviceToken != null) {
NimCore.instance.apnsService.updateApnsToken(_deviceToken!);
}
}
String loginListener = "";
TextEditingController accountEditingController =
TextEditingController(text: account);
TextEditingController passwordEditingController =
TextEditingController(text: token);
TextEditingController providerToken = TextEditingController();
TextEditingController providerExtension = TextEditingController();
TextEditingController providerTimeout = TextEditingController();
TextEditingController reConnectEditingController = TextEditingController();
//动态token
String syncToken = "";
@override
void initState() {
super.initState();
if (!kIsWeb) {
MethodChannel('com.netease.NIM.demo/settings')
.setMethodCallHandler((call) async {
if (call.method == 'updateAPNsToken') {
print('update APNs token');
_deviceToken = call.arguments as Uint8List;
}
return null;
});
_copyResources();
}
subsriptions
.add(NimCore.instance.loginService.onConnectFailed.listen((event) {
print('LoginService##onConnectFailed: ${event.toJson()}');
setState(() {
loginListener = loginListener +
'\n LoginService##onConnectFailed: ${event.toJson()}';
});
}));
subsriptions
.add(NimCore.instance.loginService.onDisconnected.listen((event) {
print('LoginService##onDisconnected: ${event.toJson()}');
setState(() {
loginListener = loginListener +
'\n LoginService##onDisconnected: ${event.toJson()}';
});
}));
subsriptions
.add(NimCore.instance.loginService.onLoginFailed.listen((event) {
print('LoginService##onLoginFailed: ${event.toJson()}');
setState(() {
loginListener =
loginListener + '\n LoginService##onLoginFailed: ${event.toJson()}';
});
}));
subsriptions
.add(NimCore.instance.loginService.onLoginStatus.listen((event) {
print('LoginService##onLoginStatus: ${event.name}');
setState(() {
loginListener =
loginListener + '\n LoginService##onLoginStatus: ${event.name}';
});
}));
subsriptions
.add(NimCore.instance.conversationService.onSyncFailed.listen((e) {
print('conversationService##onSyncFailed: ');
setState(() {
loginListener =
loginListener + '\n conversationService##onSyncFailed: ';
});
}));
subsriptions.add(NimCore.instance.teamService.onSyncFailed.listen((e) {
print('teamService##onSyncFailed: ');
setState(() {
loginListener = loginListener + '\n teamService##onSyncFailed: ';
});
}));
_doInitializeSDK();
}
void _doInitializeSDK() async {
late NIMSDKOptions options;
if (kIsWeb) {
var base = NIMInitializeOptions(
appkey: appKey,
apiVersion: 'v2',
debugLevel: 'debug',
);
options = NIMWebSDKOptions(
appKey: appKey,
initializeOptions: base,
);
} else if (Platform.isAndroid) {
final directory = await getExternalStorageDirectory();
options = NIMAndroidSDKOptions(
appKey: appKey,
enableUserInfoProvider: true,
enableMessageNotifierCustomization: true,
shouldSyncStickTopSessionInfos: true,
consoleLogEnabled: true,
sdkRootDir: directory != null ? '${directory.path}/NIMFlutter' : null,
mixPushConfig: _buildMixPushConfig());
} else if (Platform.isIOS) {
final directory = await getApplicationDocumentsDirectory();
options = NIMIOSSDKOptions(
appKey: appKey,
shouldSyncStickTopSessionInfos: true,
sdkRootDir: '${directory.path}/NIMFlutter',
apnsCername: 'ENTERPRISE',
pkCername: 'DEMO_PUSH_KIT',
);
} else if (Platform.isMacOS || Platform.isWindows) {
NIMBasicOption basicOption = NIMBasicOption();
options = NIMPCSDKOptions(basicOption: basicOption, appKey: appKey);
} else if (kIsWeb) {
var base = NIMInitializeOptions(
appkey: appKey,
);
options = NIMWebSDKOptions(
appKey: appKey,
initializeOptions: base,
);
}
NimCore.instance.initialize(options).then((value) async {
print('initialize result: $value');
});
}
Future<String> getTokenFromServer(String account) async {
var requestBody = {"appkey": appKey, "accid": account};
var header = <String, String>{
'content-type': 'application/x-www-form-urlencoded'
};
var url = Uri.parse(
"http://imtest.netease.im/nimserver/god/mockDynamicToken.action");
var response = await http.post(url, headers: header, body: requestBody);
if (response.statusCode == 200) {
var responseData = json.decode(response.body);
int code = responseData["code"];
if (code != 200) {
return "";
}
String token = responseData["data"];
return token;
} else {
return "";
}
}
Future<dynamic> createImgMsg() async {
html.File? imageObj;
String imageUrl =
'https://img2.baidu.com/it/u=1008561530,2313586183&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=1730';
http.Response response = await http.get(Uri.parse(imageUrl));
var blob = html.Blob([response.bodyBytes], 'image/jpeg', 'native');
imageObj = html.File([blob], 'image.jpg');
return MessageCreator.createImageMessage(
'',
'name',
'',
40,
40,
imageObj: imageObj,
);
}
@override
void dispose() {
subsriptions.forEach((subsription) {
subsription.cancel();
});
// 取消推送 Token 监听
_mixPushTokenSubscription?.cancel();
_mixPushTokenSubscription = null;
// 取消消息监听
_receiveMessagesSubscription?.cancel();
_receiveMessagesModifiedSubscription?.cancel();
super.dispose();
}
void loginNormal() async {
var options = NIMLoginOption();
NimCore.instance.loginService.loginExtensionProvider =
(String accountId) async {
print('dart loginExtensionProvider');
return "abd/$accountId";
};
final loginResult = await NimCore.instance.loginService.login(
accountEditingController.text, passwordEditingController.text, options);
print('login result: $loginResult');
}
void loginOffModel() async {
var options = NIMLoginOption();
options.offlineMode = true;
NimCore.instance.loginService.loginExtensionProvider =
(String accountId) async {
print('dart offlineMode loginExtensionProvider');
return "abd/$accountId";
};
final loginResult = await NimCore.instance.loginService.login(
accountEditingController.text, passwordEditingController.text, options);
loginListener = loginListener + '\n loginService##result: $loginResult';
print('login result: $loginResult');
}
void loginSync() async {
var options = NIMLoginOption();
options.authType = NIMLoginAuthType.authTypeDynamicToken;
NimCore.instance.loginService.tokenProvider = (String accountId) async {
print('login sync token : $accountId');
return providerToken.text;
};
NimCore.instance.loginService.loginExtensionProvider =
(String accountId) async {
print('dart loginExtensionProvider');
return providerExtension.text;
};
NimCore.instance.loginService.setReconnectDelayProvider((int time) async {
print('dart setReconnectDelayProvider');
return int.parse(providerTimeout.text);
});
final loginResult = await NimCore.instance.loginService.login(
accountEditingController.text, passwordEditingController.text, options);
print('login syncToken result: $loginResult');
}
///发送文本消息
void sendP2PTextMessage() async {
final message =
(await MessageCreator.createTextMessage('P2p text test message')).data;
final conversationId = (await NimCore.instance.conversationIdUtil
.p2pConversationId(friendAccount))
.data!;
if (message != null) {
message.pushConfig = NIMMessagePushConfig(forcePush: true);
final sendMessageResult = await NimCore.instance.messageService
.sendMessage(message: message, conversationId: conversationId);
if (sendMessageResult.isSuccess) {
print(
'fdsfsendMessage result: ${sendMessageResult.isSuccess} data ${sendMessageResult.data?.toJson()}');
}
}
}
void searchMessage() async {
final conversationId = (await NimCore.instance.conversationIdUtil
.p2pConversationId(friendAccount))
.data;
NimCore.instance.messageService
.searchLocalMessages(NIMMessageSearchExParams(
conversationId: conversationId,
keywordList: ['P2p text'],
limit: 5))
.then((result) {
print('Test searchLocalMessages result ${result.isSuccess}');
if (result.isSuccess && result.data != null) {
print('Test searchLocalMessages success');
result.data?.items?.forEach((e) {
e.messages?.forEach((message) {
print('Test searchLocalMessages result = ${message.text}');
});
});
}
});
}
void setMessageFilter() {
NimCore.instance.messageService.onReceiveMessages.listen((messages) {
for (var message in messages) {
print('Filter onReceiveMessages: ${message.toJson()}');
}
});
NimCore.instance.messageService.setMessageFilter((message) async {
print('setMessageFilter: ${message.toJson()}');
if (message.messageType == NIMMessageType.notification) {
return true;
}
return false;
});
}
void searchMessageLocal() async {
final conversationId =
(await NimCore.instance.conversationIdUtil.teamConversationId(teamId))
.data;
final params = NIMMessageSearchExParams(
conversationId: conversationId, keywordList: ['test'], limit: 10);
NimCore.instance.messageService.searchLocalMessages(params).then((result) {
print('searchMessageLocal message size = ${result.data?.count}');
});
}
void searchMessageCloud() async {
final conversationId =
(await NimCore.instance.conversationIdUtil.teamConversationId(teamId))
.data;
final params = NIMMessageSearchExParams(
pageToken: '',
conversationId: conversationId,
keywordList: ['test'],
limit: 10);
NimCore.instance.messageService
.searchCloudMessagesEx(params)
.then((result) {
print('searchMessageCloud message size = ${result.data?.count}');
});
}
//创建聊天室并进入
void createChatroom() async {
chatroomClient = (await V2NIMChatroomClient.newInstance()).data;
print('chatroom: ${chatroomClient?.instanceId}');
if (chatroomClient != null) {
chatroomClient!.addChatroomClientListener();
chatroomSubsriptions.addAll([
chatroomClient!.onChatroomEntered.listen((event) {
print('ChatroomClient:onChatroomEntered');
}),
chatroomClient!.onChatroomExited.listen((event) {
print('ChatroomClient:onChatroomExited');
})
]);
final links = await NimCore.instance.loginService
.getChatroomLinkAddress(chatroomId1);
Alog.d(
tag: 'ChatroomClient',
content: 'enter result:${links?.isSuccess} data ${links?.data}');
final enterParams = V2NIMChatroomEnterParams(
authType: NIMLoginAuthType.authTypeDefault,
accountId: account,
token: token);
chatroomClient!.linkProvider =
(int instanceId, String roomId, String accountId) async {
return links.data!;
};
final enterResult = await chatroomClient!.enter(chatroomId1, enterParams);
print(
'enter result:${enterResult?.isSuccess} data ${enterResult?.data?.toJson()}');
Alog.d(
tag: 'ChatroomClient',
content:
'enter result:${enterResult?.isSuccess} data ${enterResult?.data?.toJson()}');
final chatroomInfoResult = await chatroomClient?.getChatroomInfo();
print(
'chatroomInfo result: ${chatroomInfoResult?.isSuccess} data ${chatroomInfoResult?.data?.toJson()}');
Alog.d(
tag: 'ChatroomClient',
content:
'chatroomInfo result: ${chatroomInfoResult?.isSuccess} data ${chatroomInfoResult?.data?.toJson()}');
}
}
void getMessageList() async {
String? conversationId =
(await NimCore.instance.conversationIdUtil.teamConversationId(teamId))
.data;
NIMMessageListOption option =
NIMMessageListOption(conversationId: conversationId);
NimCore.instance.messageService
.getMessageList(option: option)
.then((result) {
print(
'getMessageList flutter result: ${result.isSuccess} data ${result.data?.length}');
});
}
//添加监听
void chatroomServiceListener() async {
final addresult =
await chatroomClient?.getChatroomService().addChatroomListener();
chatroomSubsriptions.addAll([
chatroomClient!.getChatroomService().onReceiveMessages.listen((event) {
print('getChatroomService:onReceiveMessages');
}),
chatroomClient!.getChatroomService().onSendMessage.listen((event) {
print('getChatroomService:onSendMessage ${event.message?.toJson()}');
}),
chatroomClient!
.getChatroomService()
.onSendMessageProgress
.listen((event) {
print(
'getChatroomService:onSendMessageProgress ${event.messageClientId} process ${event.progress}');
}),
]);
}
void sendChatroomTextMessage() async {
final message = (await V2NIMChatroomMessageCreator.createTextMessage(
'test text chatroom message'))
.data;
final chatroomService = chatroomClient?.getChatroomService();
if (message != null) {
V2NIMSendChatroomMessageParams params = V2NIMSendChatroomMessageParams();
final messageSender = await chatroomService?.sendMessage(message, params);
print(
'messageSender: ${messageSender?.isSuccess} data ${messageSender?.data?.toJson()}');
Alog.d(
tag: 'ChatroomService',
content:
'messageSender: ${messageSender?.isSuccess} data ${messageSender?.data?.toJson()}');
}
}
void sendChatroomVideoMessage() async {
final videoPath = Platform.isAndroid
? (await getExternalStorageDirectory())!.path + "/test.mp4"
: (await getApplicationDocumentsDirectory()).path + "/test.mp4";
if (!File(videoPath).existsSync()) {
print('File $videoPath 不存在');
}
final message = (await V2NIMChatroomMessageCreator.createVideoMessage(
videoPath,
duration: 70000,
width: 102,
height: 150))
.data;
final chatroomService = chatroomClient?.getChatroomService();
if (message != null) {
V2NIMSendChatroomMessageParams params = V2NIMSendChatroomMessageParams();
final messageSender = await chatroomService?.sendMessage(message, params);
print(
'messageSender: ${messageSender?.isSuccess} data ${messageSender?.data?.toJson()}');
Alog.d(
tag: 'ChatroomService',
content:
'messageSender: ${messageSender?.isSuccess} data ${messageSender?.data?.toJson()}');
}
}
void sendChatroomImageMessage() async {
final imagePath = Platform.isAndroid
? (await getExternalStorageDirectory())!.path + "/test.jpg"
: (await getApplicationDocumentsDirectory()).path + "/test.jpg";
if (!File(imagePath).existsSync()) {
print('File $imagePath 不存在');
}
final message = (await V2NIMChatroomMessageCreator.createImageMessage(
imagePath,
width: 0,
height: 0))
.data;
final chatroomService = chatroomClient?.getChatroomService();
if (message != null) {
V2NIMSendChatroomMessageParams params = V2NIMSendChatroomMessageParams();
final messageSender = await chatroomService?.sendMessage(message, params);
print(
'messageSender: ${messageSender?.isSuccess} data ${messageSender?.data?.toJson()}');
Alog.d(
tag: 'ChatroomService',
content:
'messageSender: ${messageSender?.isSuccess} data ${messageSender?.data?.toJson()}');
}
}
void sendChatroomAudioMessage() async {
final audioPath = Platform.isAndroid
? (await getExternalStorageDirectory())!.path + "/test.mp3"
: (await getApplicationDocumentsDirectory()).path + "/test.mp3";
if (!File(audioPath).existsSync()) {
print('File $audioPath 不存在');
}
final message = (await V2NIMChatroomMessageCreator.createAudioMessage(
audioPath,
duration: 6000))
.data;
final chatroomService = chatroomClient?.getChatroomService();
if (message != null) {
V2NIMSendChatroomMessageParams params = V2NIMSendChatroomMessageParams();
final audioMessageSender =
await chatroomService?.sendMessage(message, params);
print(
'audioMessageSender: ${audioMessageSender?.isSuccess} data ${audioMessageSender?.data?.toJson()}');
Alog.d(
tag: 'ChatroomService',
content:
'audioMessageSender: ${audioMessageSender?.isSuccess} data ${audioMessageSender?.data?.toJson()}');
}
}
// void getMessageList() async {
// final chatroomService = chatroomClient?.getChatroomService();
// if (chatroomService != null) {
// final option = V2NIMChatroomMessageListOption(limit: 2);
// final messageList = (await chatroomService.getMessageList(option));
// if (messageList.data != null) {
// messageList.data!.forEach((message) {
// print('messageList : ${message.toJson()}');
// });
// }
// }
// }
void addQueueListener() {
final chatroomQueue = chatroomClient?.getChatroomQueueService();
chatroomQueue?.addQueueListener();
chatroomSubsriptions.addAll([
chatroomQueue!.onChatroomQueueOffered.listen((event) {
print('onChatroomQueueOffered: ${event.toJson()}');
}),
chatroomQueue.onChatroomQueuePolled.listen((event) {
print('onChatroomQueuePolled: ${event.toJson()}');
}),
chatroomQueue.onChatroomQueueBatchUpdated.listen((event) {
print('onChatroomQueueBatchUpdated: ${event.length}');
})
]);
}
void queueOffer() async {
final params = V2NIMChatroomQueueOfferParams(
elementKey: 'testKey', elementValue: 'testValue');
final chatroomQueue = chatroomClient?.getChatroomQueueService();
final result = await chatroomQueue?.queueOffer(params);
print('queueOffer result: ${result?.isSuccess} data ');
}
void queuePeek() async {
final chatroomQueue = chatroomClient?.getChatroomQueueService();
final result = await chatroomQueue?.queuePeek();
print(
'queuePeek result: ${result?.isSuccess} data ${result?.data?.toJson()}');
final updateResult = await chatroomQueue?.queueBatchUpdate([result!.data!]);
print('queueBatchUpdate result: ${updateResult?.isSuccess} data');
}
void queueInit() async {
final chatroomQueue = chatroomClient?.getChatroomQueueService();
final result = await chatroomQueue?.queueInit(3);
print('queueInit result: ${result?.isSuccess} ');
}
void queueList() async {
final chatroomQueue = chatroomClient?.getChatroomQueueService();
final result = await chatroomQueue?.queueList();
print(
'queueList result: ${result?.isSuccess} data ${result?.data?.length}');
}
void getAIUserList() async {
NimCore.instance.aiService.getAIUserList().then((result) {
print('getAIUserList result: ${result.isSuccess}');
if (result.data?.isNotEmpty == true) {
for (var element in result.data!) {
print('getAIUserList element: ${element.toJson()}');
}
}
});
}
void _copyResources() async {
// copy image
var bytes = await rootBundle.load("resources/test.jpg");
Directory? documentDir = await getApplicationDocumentsDirectory();
File tempFile = new File("${documentDir?.path}/test.jpg");
if (!tempFile.existsSync()) {
tempFile.createSync();
}
tempFile.writeAsBytes(
bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes));
// copy mp3
bytes = await rootBundle.load("resources/test.mp3");
tempFile = new File("${documentDir?.path}/test.mp3");
if (!tempFile.existsSync()) {
tempFile.createSync();
}
tempFile.writeAsBytes(
bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes));
// copy mp4
bytes = await rootBundle.load("resources/test.mp4");
tempFile = new File("${documentDir?.path}/test.mp4");
if (!tempFile.existsSync()) {
tempFile.createSync();
}
tempFile.writeAsBytes(
bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes));
}
/// 注册手动提供 Token 回调
bool _isManualTokenCallbackRegistered = false;
void _registerManualTokenCallback() {
if (_isManualTokenCallbackRegistered) {
// 已注册,传 null 取消
NimCore.instance.mixPushService
.registerManuallyProvidePushTokenCallback(null);
_isManualTokenCallbackRegistered = false;
print('手动 Token 回调已取消');
setState(() {
loginListener = loginListener + '\n 🔕 手动 Token 回调已取消';
});
} else {
// 未注册,注册回调
NimCore.instance.mixPushService.registerManuallyProvidePushTokenCallback(
(pushType) {
print('收到手动 Token 请求, pushType: $pushType');
// 注意:这里不能使用 setState,因为回调可能在后台线程执行
// 这里模拟返回一个测试 Token
// 实际场景中应该根据 pushType 从对应厂商 SDK 获取真实 Token
return NIMMixPushToken(
pushType: pushType,
token:
'aq/LX2Yijj3DxfJ/sNGIKq/tKS9/F/XvHo1jgrGYxfEJEb4rZlSQFo8v8xSNLGs7',
);
},
);
_isManualTokenCallbackRegistered = true;
print('手动 Token 回调已注册');
setState(() {
loginListener = loginListener + '\n 🔔 手动 Token 回调已注册';
});
}
}
/// 消息监听订阅
StreamSubscription<List<NIMMessage>>? _receiveMessagesSubscription;
StreamSubscription<List<NIMMessage>>? _receiveMessagesModifiedSubscription;
/// 监听推送 Token 变化事件
///
/// 订阅 onMixPushToken 事件流,当推送 Token 发生变化时会收到回调
/// 适用于:
/// 1. 正常模式:SDK 自动获取厂商 Token 后通知
/// 2. 手动模式:手动提供 Token 后确认生效
StreamSubscription<NIMMixPushToken>? _mixPushTokenSubscription;
void _observeMixPushToken() {
// 避免重复订阅
if (_mixPushTokenSubscription != null) {
print('推送 Token 监听已存在,请勿重复注册');
setState(() {
loginListener = loginListener + '\n ⚠️ 推送 Token 监听已存在';
});
return;
}
_mixPushTokenSubscription =
NimCore.instance.mixPushService.onMixPushToken.listen(
(token) {
print('收到推送 Token 变化通知:');
print(' - pushType: ${token.pushType}');
print(' - token: ${token.token}');
print(' - tokenName: ${token.tokenName}');
setState(() {
loginListener = loginListener +
'\n ✅ 收到推送 Token:' +
'\n pushType: ${token.pushType}' +
'\n token: ${_truncateToken(token.token)}' +
'\n tokenName: ${token.tokenName ?? "N/A"}';
});
},
onError: (error) {
print('推送 Token 监听发生错误: $error');
setState(() {
loginListener = loginListener + '\n ❌ 推送 Token 监听错误: $error';
});
},
);
print('推送 Token 监听已注册');
setState(() {
loginListener = loginListener + '\n 🔔 推送 Token 监听已注册';
});
}
/// 截断过长的 Token 用于显示
String _truncateToken(String? token) {
if (token == null || token.isEmpty) return 'N/A';
if (token.length <= 20) return token;
return '${token.substring(0, 10)}...${token.substring(token.length - 10)}';
}
/// 打印 streamConfig 完整内容到控制台
void _printStreamConfig(V2NIMMessageStreamConfig? streamConfig) {
print(' - streamConfig: ${streamConfig != null ? "存在" : "null"}');
if (streamConfig != null) {
// 1. 流式消息状态
print(' - status: ${streamConfig.status}');
// 2. 最近一个分片 lastChunk
final lastChunk = streamConfig.lastChunk;
print(' - lastChunk: ${lastChunk != null ? "存在" : "null"}');
if (lastChunk != null) {
print(' - content: ${lastChunk.content}');
print(' - type: ${lastChunk.type}');
print(' - index: ${lastChunk.index}');
print(' - messageTime: ${lastChunk.messageTime}');
print(' - chunkTime: ${lastChunk.chunkTime}');
}
// 3. RAG 引用资源列表
final rags = streamConfig.rags;
print(' - rags: ${rags != null ? "${rags.length} 个" : "null"}');
if (rags != null && rags.isNotEmpty) {
for (var i = 0; i < rags.length; i++) {
final rag = rags[i];
print(' [$i] name: ${rag.name}');
print(' title: ${rag.title}');
print(' description: ${rag.description}');
print(' url: ${rag.url}');
print(' icon: ${rag.icon}');
print(' time: ${rag.time}');
}
}
}
}
/// 构建 streamConfig 信息用于界面显示
String _buildStreamConfigInfo(V2NIMMessageStreamConfig? streamConfig) {
if (streamConfig == null) {
return '\n streamConfig: ❌ null';
}
final buffer = StringBuffer();
buffer.write('\n streamConfig: ✅ 存在');
// 1. 状态
buffer.write('\n - status: ${streamConfig.status}');
// 2. lastChunk
final lastChunk = streamConfig.lastChunk;
if (lastChunk != null) {
buffer.write('\n - lastChunk: ✅ 存在');
buffer.write('\n - content: ${_truncateToken(lastChunk.content)}');
buffer.write('\n - type: ${lastChunk.type}');
buffer.write('\n - index: ${lastChunk.index}');
buffer.write('\n - messageTime: ${lastChunk.messageTime}');
buffer.write('\n - chunkTime: ${lastChunk.chunkTime}');
} else {
buffer.write('\n - lastChunk: null');
}
// 3. rags
final rags = streamConfig.rags;
if (rags != null && rags.isNotEmpty) {
buffer.write('\n - rags: ${rags.length} 个');
for (var i = 0; i < rags.length && i < 2; i++) {
// 最多显示2个
final rag = rags[i];
buffer.write('\n [$i] name: ${rag.name}');
buffer.write('\n title: ${_truncateToken(rag.title)}');
buffer.write('\n url: ${_truncateToken(rag.url)}');
}
if (rags.length > 2) {
buffer.write('\n ... 还有 ${rags.length - 2} 个');
}
} else {
buffer.write('\n - rags: ${rags == null ? "null" : "空列表"}');
}
return buffer.toString();
}
/// 取消推送 Token 监听
void _cancelMixPushTokenObserver() {
if (_mixPushTokenSubscription != null) {
_mixPushTokenSubscription!.cancel();
_mixPushTokenSubscription = null;
print('推送 Token 监听已取消');
setState(() {
loginListener = loginListener + '\n 🔕 推送 Token 监听已取消';
});
} else {
print('没有活跃的推送 Token 监听');
setState(() {
loginListener = loginListener + '\n ⚠️ 没有活跃的推送 Token 监听';
});
}
}
/// 是否已开启消息监听
bool _isMessageListenerRegistered = false;
/// 注册消息监听 (onReceiveMessages & onReceiveMessagesModified)
void _registerMessageListener() {
if (_isMessageListenerRegistered) {
// 已注册,取消监听
_cancelMessageListener();
return;
}
final messageService = NimCore.instance.messageService;
// 监听接收消息 onReceiveMessages
_receiveMessagesSubscription = messageService.onReceiveMessages.listen(
(messages) {
print('onReceiveMessages: 收到新消息 ${messages.length} 条');
for (var msg in messages) {
print(' - 发送者: ${msg.senderId}');
print(' - 类型: ${msg.messageType}');
print(' - 内容: ${msg.text ?? "[非文本]"}');
print(' - messageClientId: ${msg.messageClientId}');
// 打印 streamConfig 完整内容
_printStreamConfig(msg.streamConfig);
}
final firstMsg = messages.firstOrNull;
final streamInfo = _buildStreamConfigInfo(firstMsg?.streamConfig);
setState(() {
loginListener = loginListener +
'\n 📩 onReceiveMessages: ${messages.length} 条' +
'\n 发送者: ${firstMsg?.senderId ?? "N/A"}' +
'\n 类型: ${firstMsg?.messageType}' +
'\n 内容: ${_truncateToken(firstMsg?.text)}' +
streamInfo;
});
},
onError: (error) {
print('onReceiveMessages 监听错误: $error');
setState(() {
loginListener = loginListener + '\n ❌ onReceiveMessages 错误: $error';
});
},
);
// 监听消息修改 onReceiveMessagesModified
_receiveMessagesModifiedSubscription =
messageService.onReceiveMessagesModified.listen(
(messages) {
print('onReceiveMessagesModified: 消息被修改 ${messages.length} 条');
for (var msg in messages) {
print(' - messageClientId: ${msg.messageClientId}');
print(' - 类型: ${msg.messageType}');
print(' - 发送者: ${msg.senderId}');
// 打印 streamConfig 完整内容
_printStreamConfig(msg.streamConfig);
}
final firstMsg = messages.firstOrNull;
final streamInfo = _buildStreamConfigInfo(firstMsg?.streamConfig);
setState(() {
loginListener = loginListener +
'\n ✏️ onReceiveMessagesModified: ${messages.length} 条' +
'\n messageClientId: ${firstMsg?.messageClientId ?? "N/A"}' +
'\n 发送者: ${firstMsg?.senderId}' +
'\n 类型: ${firstMsg?.messageType}' +
streamInfo;
});
},
onError: (error) {
print('onReceiveMessagesModified 监听错误: $error');
setState(() {
loginListener =
loginListener + '\n ❌ onReceiveMessagesModified 错误: $error';
});
},
);
_isMessageListenerRegistered = true;
print('消息监听已注册 (onReceiveMessages & onReceiveMessagesModified)');
setState(() {
loginListener = loginListener + '\n 🔔 消息监听已注册';
});
}
/// 取消消息监听
void _cancelMessageListener() {
_receiveMessagesSubscription?.cancel();
_receiveMessagesSubscription = null;
_receiveMessagesModifiedSubscription?.cancel();
_receiveMessagesModifiedSubscription = null;
_isMessageListenerRegistered = false;
print('消息监听已取消');
setState(() {
loginListener = loginListener + '\n 🔕 消息监听已取消';
});
}
/// 构建推送配置(参考 im_demo 配置)
NIMMixPushConfig _buildMixPushConfig() {
return NIMMixPushConfig(
// xiaomi
xmAppId: '2882303761520055541',
xmAppKey: '5222005592541',
xmCertificateName: 'KIT_FLUTTER_MI_PUSH',
// huawei
hwAppId: '106776305',
hwCertificateName: 'KIT_FLUTTER_HW_PUSH',
// meizu
mzAppId: '149497',
mzAppKey: '59aea173afc94791ad271f7d51e4bded',
mzCertificateName: 'KIT_FLUTTER_MEIZU_PUSH',
// vivo
vivoCertificateName: 'KIT_FLUTTER_VIVO_PUSH',
// oppo
oppoAppId: '30853511',
oppoAppKey: 'b2fe114b4f744f0ca6855731d18a2d54',
oppoAppSecret: 'dc093c8c4d154722a75cc3a69af73ce9',
oppoCertificateName: 'KIT_FLUTTER_OPPO_PUSH',
// 启用手动提供 Push Token 模式
manualProvidePushToken: true,
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: SingleChildScrollView(
child: Column(
children: [
Text('登录回调'),
Text(
loginListener,
),
Text('账号'),
TextField(
controller: accountEditingController,
),
Text('token'),
TextField(
controller: passwordEditingController,
),
Text('动态token'),
TextField(
controller: providerToken,
),
Text('登录扩展'),
TextField(
controller: providerExtension,
),
Text('登录重连延时回调'),
TextField(
controller: providerTimeout,
keyboardType: TextInputType.number, // 弹出数字键盘
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly // 只允许输入数字
],
),
TextButton(
onPressed: () {
loginNormal();
},
child: Text('普通登录')),
TextButton(
onPressed: () {
loginOffModel();
},
child: Text('离线登录')),
TextButton(
onPressed: () {
sendP2PTextMessage();
},
child: Text('发送消息')),
TextButton(
onPressed: () {
searchMessage();
},
child: Text('搜索消息')),
TextButton(
onPressed: () {
searchMessageLocal();
},
child: Text('本地查询')),
TextButton(
onPressed: () {
searchMessageCloud();
},
child: Text('云端查询')),
TextButton(
onPressed: () {
getAIUserList();
},
child: Text('获取AI用户')),
// TextButton(
// onPressed: () {
// chatroomServiceListener();
// },
// child: Text('添加聊天室服务')),
// TextButton(
// onPressed: () {
// addQueueListener();
// },
// child: Text('添加队列监听')),
// TextButton(
// onPressed: () {
// queueInit();
// },
// child: Text('初始化队列')),
// TextButton(
// onPressed: () {
// queueOffer();
// },
// child: Text('新增队列')),
// TextButton(
// onPressed: () {
// queueList();
// },
// child: Text('拉取队列')),
// TextButton(
// onPressed: () {
// queuePeek();
// },
// child: Text('更新队列')),
// TextButton(
// onPressed: () {
// sendChatroomTextMessage();
// },
// child: Text('发送消息')),
// TextButton(
// onPressed: () {
// sendChatroomImageMessage();
// },
// child: Text('发送图片消息')),
// TextButton(
// onPressed: () {
// sendChatroomVideoMessage();
// },
// child: Text('发送视频消息')),
// TextButton(
// onPressed: () {
// sendChatroomAudioMessage();
// },
// child: Text('发送语音消息')),
TextButton(
onPressed: () {
_registerManualTokenCallback();
},
child: Text(_isManualTokenCallbackRegistered
? '取消手动Token回调'
: '注册手动Token回调')),
TextButton(
onPressed: () {
_observeMixPushToken();
},
child: Text('监听推送Token')),
TextButton(
onPressed: () {
_cancelMixPushTokenObserver();
},
child: Text('取消Token监听')),
TextButton(
onPressed: () {
_registerMessageListener();
},
child:
Text(_isMessageListenerRegistered ? '取消消息监听' : '注册消息监听')),
TextButton(
onPressed: () {
setState(() {
loginListener = "";
});
},
child: Text('清空回调信息')),
],
),
),
),
);
}
}