Commit a64e1870 by Administrator

Merge branch 'develop251027' of http://120.24.213.56/ethan/appframe into develop251027

2 parents 8a6643f3 e510e0ed
import 'package:appframe/config/db.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/services/mqtt_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mqtt_client/mqtt_client.dart';
class MqttState extends Equatable {
final List<String> messages;
const MqttState({this.messages = const []});
MqttState copyWith({List<String>? messages}) {
return MqttState(
messages: messages ?? this.messages,
);
}
@override
List<Object?> get props => [messages];
}
class MqttCubit extends Cubit<MqttState> with WidgetsBindingObserver {
late MqttService _mqttService;
MqttCubit() : super(MqttState()) {
_mqttService = getIt.get<MqttService>();
_mqttService.listen(_onMessageReceived);
WidgetsBinding.instance.addObserver(this);
}
void goWeb() {
router.go('/web');
}
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
// print('----------------------didChangeAppLifecycleState------------------------------');
if (state == AppLifecycleState.resumed) {
// print('----------------------resumed------------------------------');
if (!_mqttService.isConnected()) {
// print('----------------------reconnected------------------------------');
await _mqttService.initConn();
_mqttService.listen(_onMessageReceived);
}
}
}
Future<void> _onMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) async {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('-------------------------> $pt');
// Map<String, dynamic> data = {"id": 1, "content": pt};
// bxeDb.insert('msg', data);
//
// var msgList = await bxeDb.query('msg');
// for (var msg in msgList) {
// print('-------------------------> $msg');
// }
// 处理接收到的消息
emit(state.copyWith(messages: [...state.messages, pt]));
}
@override
Future<void> close() {
// 关闭时移除观察者
WidgetsBinding.instance.removeObserver(this);
return super.close();
}
}
......@@ -229,11 +229,14 @@ class WebCubit extends Cubit<WebState> {
final String serverUrl;
if (state.sessionCode == null || state.sessionCode == '') {
serverUrl = '${Constant.localServerTestFileUrl}/login.html';
serverUrl = '${Constant.localServerUrl}/index.html';
// serverUrl = '${Constant.localServerTestFileUrl}/login.html';
// serverUrl = '${Constant.localServerTestFileUrl}/test2.html';
} else {
// serverUrl =
// 'http://${state.ip}:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
serverUrl =
'http://${state.ip}:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
'${Constant.localServerUrl}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
}
_controller.loadRequest(Uri.parse(serverUrl));
......@@ -275,6 +278,10 @@ class WebCubit extends Cubit<WebState> {
router.go('/loginMain');
}
void goMqtt(){
router.go('/mqtt');
}
//测试
void goAuth() {
// String serverUrl = 'http://${state.ip}:${_server.port}/index.html';
......@@ -749,7 +756,6 @@ class WebCubit extends Cubit<WebState> {
try {
await _recorder?.closeRecorder();
} catch (e) {
// ignore: empty_catches
print(e);
}
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
......@@ -851,7 +857,6 @@ class WebCubit extends Cubit<WebState> {
try {
await _player?.closePlayer();
} catch (e) {
// ignore: empty_catches
print(e);
}
emit(state.copyWith(playerIsInit: false, playState: 0, playId: ''));
......@@ -868,14 +873,12 @@ class WebCubit extends Cubit<WebState> {
try {
await _recorder?.closeRecorder();
} catch (e) {
// ignore: empty_catches
print(e);
}
try {
await _player?.closePlayer();
} catch (e) {
// ignore: empty_catches
print(e);
}
......
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
late final Database bxeDb;
Future<void> initDatabase() async {
// 获取数据库存储的默认路径
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'bxe_database.db');
// 打开数据库,如果不存在则创建
Database database = await openDatabase(
path,
version: 1, // 数据库版本
onCreate: (Database db, int version) async {
// 创建表
await db.execute(
// 'CREATE TABLE msg(id INTEGER PRIMARY KEY, content TEXT)',
'CREATE TABLE msg(id INTEGER, content TEXT)',
);
},
);
bxeDb = database;
}
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/services.dart';
late HttpServer localServer;
Future<void> startLocalServer() async {
HttpServer localServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
// print('本地服务器启动在端口: ${localServer.port}');
localServer.listen((HttpRequest request) async {
final String requestPath = request.uri.path == '/' ? '/index.html' : request.uri.path;
try {
if (requestPath.startsWith('/temp/')) {
// 临时目录文件的请求
await _serveTempFile(request, requestPath);
} else if (requestPath.startsWith('/test/')) {
// assets文件服务逻辑
await _serveAssetFile(request, requestPath);
} else {
// asset/dist.zip 文件服务逻辑
await _serveZipFileContent(request, requestPath);
}
} catch (e) {
print('处理请求时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Internal server error')
..close();
}
});
}
// 临时目录的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try {
// 临时文件已经设备路径
// 构建文件路径(移除 /temp 前缀)
final String filePath = requestPath.substring('/temp/'.length);
// 检查文件是否存在
final File file = File(filePath);
if (await file.exists()) {
// 读取文件内容
final List<int> bytes = await file.readAsBytes();
request.response
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
} else {
request.response
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
} catch (e) {
print('读取临时文件时出错: $e');
request.response
..statusCode = HttpStatus.notFound
..write('File not found')
..close();
}
}
Future<void> _serveZipFileContent(HttpRequest request, String requestPath) async {
String zipAssetPath = 'assets/dist.zip';
try {
// 使用 rootBundle.load 加载资源文件
final ByteData data = await rootBundle.load(zipAssetPath);
final List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// 读取并解压zip文件内容
final Archive archive = ZipDecoder().decodeBytes(bytes);
// 查找请求的内部文件
ArchiveFile? targetFile;
for (final file in archive) {
// 标准化路径分隔符(统一使用 '/')
String zipFileName = file.name.replaceAll('\\', '/');
// 移除开头的 '/'(如果存在)
if (requestPath.startsWith('/')) {
requestPath = requestPath.substring(1);
}
if (zipFileName == requestPath) {
targetFile = file;
break;
}
}
if (targetFile == null) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found in zip: $requestPath')
..close();
return;
}
// 返回文件内容
request.response
..headers.contentType = ContentType.parse(_getContentType(requestPath))
..add(targetFile.content as List<int>)
..close();
} catch (e) {
print('读取zip文件时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Error reading zip file')
..close();
}
}
// 访问assets目录下的文件
Future<void> _serveAssetFile(HttpRequest request, String requestPath) async {
// 构建文件路径(移除 /test 前缀)
final String path = requestPath.substring('/test'.length);
final String filePath = 'assets$path';
try {
final ByteData data = await rootBundle.load(filePath);
final List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
request.response
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
} catch (e) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
}
String _getContentType(String filePath) {
if (filePath.endsWith('.html')) return 'text/html';
if (filePath.endsWith('.js')) return 'application/javascript';
if (filePath.endsWith('.css')) return 'text/css';
if (filePath.endsWith('.png')) return 'image/png';
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
return 'application/octet-stream';
}
......@@ -41,7 +41,7 @@ Future<void> setupLocator() async {
getIt.registerSingleton<Fluwx>(
await (() async {
Fluwx fluwx = Fluwx();
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://univerallink.banxe.cn/link/");
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://dev.banxiaoer.net/path/to/wechat/");
return fluwx;
})(),
);
......@@ -169,6 +169,7 @@ Future<void> setupLocator() async {
/// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net'));
/// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/mqtt_service.dart';
/// 此处暂时测试
/// 正常需要在登录状态下,查询host和jwt
Future<void> registerMqtt() async {
String mqttHost = '58.87.99.45';
int mqttPort = 1883;
String mqttClientId = 'asdfasdf';
int keepAlive = 60;
String mqttUsername = 'user';
String mqttPassword =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjIzOTY5MDQsInJzIjoib2sifQ.tsouW-uHTe6ndiEqSE4wcR_oSScuoS0qVvqWtNSaXgWkX2yEja9ImnMYr6jNKxJY8_qox9UdXRuCept5cTM4-8ZaA0BBlYXwr3LbC3yPmniFhs-tAMFbZqG2-3r0sc5NMbE3M1fXMi3dmvQc2AlyazheL98EmRNILFsz6pZ-x5rR1gFulZ53PXa7fX60XRlXg8jc3-89gdUJcS0MOMU7yyU0Sv3QBBplLr3CmWCoYX99a_3QHvq3o7aUTJ_Ed5Ms9_3k4QHgawsfdTWjZkyBouAvLMgVkt4D0PJbhgsjzAaA01Q7jEJ_SokNojOFfQYuHNScczmlOLXocJCCD4189A';
/// 初始化MQTT客户端
var mqttService = MqttService(mqttHost, mqttPort, mqttClientId, keepAlive, mqttUsername, mqttPassword);
await mqttService.initConn();
/// 设置到getIt,用于获取使用
getIt.registerSingleton(mqttService);
}
......@@ -2,6 +2,7 @@ import 'package:appframe/ui/pages/adv_page.dart';
import 'package:appframe/ui/pages/link_page.dart';
import 'package:appframe/ui/pages/login_main_page.dart';
import 'package:appframe/ui/pages/login_phone_page.dart';
import 'package:appframe/ui/pages/mqtt_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart';
import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/pages/wechat_auth_page.dart';
......@@ -53,5 +54,11 @@ final GoRouter router = GoRouter(
return const AdvPage();
},
),
GoRoute(
path: '/mqtt',
builder: (BuildContext context, GoRouterState state) {
return const MqttPage();
},
),
],
);
import 'package:fluwx/fluwx.dart';
final Fluwx fluwx = Fluwx();
Future<void> registerWechatApi() async {
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://dev.banxiaoer.net/path/to/wechat/");
}
......@@ -15,14 +15,18 @@ class AudioPlayHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
final url = params['url'] as String;
bool result = await _webCubit!.playAudio(url);
return result;
bool result = await _webCubit!.playAudio(url);
return result;
} finally {
_unfollowCubit();
}
}
}
......
......@@ -15,8 +15,12 @@ class AudioRecorderStartHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.startRecording();
return result;
try {
bool result = await _webCubit!.startRecording();
return result;
} finally {
_unfollowCubit();
}
}
}
......@@ -34,8 +38,12 @@ class AudioRecorderPauseHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.pauseRecording();
return result;
try {
bool result = await _webCubit!.pauseRecording();
return result;
} finally {
_unfollowCubit();
}
}
}
......@@ -53,8 +61,12 @@ class AudioRecorderResumeHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.resumeRecording();
return result;
try {
bool result = await _webCubit!.resumeRecording();
return result;
} finally {
_unfollowCubit();
}
}
}
......@@ -72,7 +84,11 @@ class AudioRecorderStopHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
return await _webCubit!.stopRecording();
try {
return await _webCubit!.stopRecording();
} finally {
_unfollowCubit();
}
}
}
......@@ -90,6 +106,10 @@ class AudioRecorderClearHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
return await _webCubit!.clearRecording();
try {
return await _webCubit!.clearRecording();
} finally {
_unfollowCubit();
}
}
}
......@@ -21,30 +21,34 @@ class ChooseImageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
var sourceType = params['sourceType'] as String;
if (sourceType != 'album' && sourceType != 'camera') {
throw Exception('参数错误');
}
// 暂时忽略对此参数的处理
List<dynamic>? sizeType;
if (params.containsKey('sizeType')) {
sizeType = params['sizeType'] as List<dynamic>;
if (sizeType.isEmpty || sizeType.length > 2) {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
}
int count = 9;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
var sourceType = params['sourceType'] as String;
if (sourceType != 'album' && sourceType != 'camera') {
throw Exception('参数错误');
}
}
// 暂时忽略对此参数的处理
List<dynamic>? sizeType;
if (params.containsKey('sizeType')) {
sizeType = params['sizeType'] as List<dynamic>;
if (sizeType.isEmpty || sizeType.length > 2) {
throw Exception('参数错误');
}
}
int count = 9;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
throw Exception('参数错误');
}
}
_webCubit!.setChooseImageCmdFlag(true, _message!);
_webCubit!.setChooseImageCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
......@@ -21,159 +21,34 @@ class ChooseVideoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
var sourceType = params['sourceType'] as String;
if (sourceType != 'album' && sourceType != 'camera') {
throw Exception('参数错误');
}
int count = 1;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
}
int maxDuration = 60;
if (params.containsKey('maxDuration')) {
maxDuration = params['maxDuration'] as int;
if (maxDuration < 1 || maxDuration > 600) {
var sourceType = params['sourceType'] as String;
if (sourceType != 'album' && sourceType != 'camera') {
throw Exception('参数错误');
}
}
_webCubit!.setChooseVideoCmdFlag(true, _message!);
int count = 1;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
throw Exception('参数错误');
}
}
int maxDuration = 60;
if (params.containsKey('maxDuration')) {
maxDuration = params['maxDuration'] as int;
if (maxDuration < 1 || maxDuration > 600) {
throw Exception('参数错误');
}
}
_webCubit!.setChooseVideoCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
// class ChooseVideoHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(params) async {
// if (params is! Map<String, dynamic>) {
// throw Exception('参数错误');
// }
// var sourceType = params['sourceType'] as String;
// if (sourceType != 'album' && sourceType != 'camera') {
// throw Exception('参数错误');
// }
// // 暂时忽略对此参数的处理
// List<dynamic>? sizeType;
// if (params.containsKey('sizeType')) {
// sizeType = params['sizeType'] as List<dynamic>;
// if (sizeType.isEmpty || sizeType.length > 2) {
// throw Exception('参数错误');
// }
// }
//
// int count = 1;
// if (params.containsKey('count')) {
// count = params['count'] as int;
// if (count < 1 || count > 9) {
// throw Exception('参数错误');
// }
// }
//
// int number = 60;
// if (params.containsKey('number')) {
// number = params['number'] as int;
// if (number < 1 || number > 60) {
// throw Exception('参数错误');
// }
// }
//
// // 从相册选择
// if (sourceType == 'album') {
// if (count == 1) {
// return await _selectSingleVideo();
// } else {
// return await _selectMultiVideo(count);
// }
// }
// // 拍摄
// else {
// return await _cameraSingle();
// }
// }
//
// Future<List<dynamic>> _selectSingleVideo() async {
// final ImagePicker picker = ImagePicker();
// final XFile? pickedVideo = await picker.pickVideo(source: ImageSource.gallery);
//
// // 用户取消选择,返回空数组
// if (pickedVideo == null) {
// return [];
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// return [await _handleOne(pickedVideo, tempDir)];
// }
//
// Future<List<dynamic>> _selectMultiVideo(int limit) async {
// final ImagePicker picker = ImagePicker();
// final List<XFile> pickedVideoList = await picker.pickMultiVideo(limit: limit);
//
// // 用户取消选择,返回空数组
// if (pickedVideoList.isEmpty) {
// return [];
// }
//
// // 限制最多limit张
// if (pickedVideoList.length > limit) {
// pickedVideoList.removeRange(limit, pickedVideoList.length);
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// final List<Map<String, dynamic>> result = [];
// for (final XFile? file in pickedVideoList) {
// if (file != null) {
// result.add(await _handleOne(file, tempDir));
// }
// }
// return result;
// }
//
// ///
// /// 拍照
// ///
// Future<List<Map<String, dynamic>>?> _cameraSingle() async {
// final ImagePicker picker = ImagePicker();
//
// final XFile? pickedFile = await picker.pickVideo(source: ImageSource.camera);
//
// // 用户取消选择,返回空数组
// if (pickedFile == null) {
// return [];
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// return [await _handleOne(pickedFile, tempDir)];
// }
//
// Future<Map<String, dynamic>> _handleOne(XFile pickedFile, Directory tempDir) async {
// var sourceFile = File(pickedFile.path);
//
// final thumbnailPath = await ThumbnailUtil.genVideoThumbnail(pickedFile.path, tempDir);
// // 暂时这样进行接口测试
// // 根据视频预览图,获取视频的宽度和高度
// final sizeResult = ImageSizeGetter.getSizeResult(FileInput(File(thumbnailPath!)));
//
// // 返回一个元素的数组
// return {
// "tempFilePath": "/temp${sourceFile.path}",
// "size": sourceFile.lengthSync(),
// "width": sizeResult.size.width,
// "height": sizeResult.size.height,
// "thumbTempFilePath": '/temp$thumbnailPath',
// "fileType": sourceFile.path.split('/').last.split('.').last,
// };
// }
// }
......@@ -15,7 +15,11 @@ class GoLoginHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
_webCubit!.goLogin();
return;
try {
_webCubit!.goLogin();
return;
} finally {
_unfollowCubit();
}
}
}
......@@ -21,6 +21,10 @@ class OrientationHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
_webCubit!.setOrientationCmdFlag(true, _message!);
try {
_webCubit!.setOrientationCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
import 'dart:ffi';
import 'package:appframe/services/dispatcher.dart';
import 'package:flutter_sound/flutter_sound.dart';
FlutterSoundPlayer? localPlayer;
bool isLocalPlaying = false;
bool isLocalPausePlaying = false;
String? localPlayedFilePath;
Future<bool> initLocalPlayer() async {
localPlayer = FlutterSoundPlayer();
try {
await localPlayer!.openPlayer();
} catch (e) {
print(e);
await _resetPlayerStatus();
throw Exception('打开播放器失败!');
}
return true;
}
Future<bool> startLocalPlayer(String url) async {
if (localPlayer == null) {
await initLocalPlayer();
}
try {
localPlayedFilePath = url;
localPlayer!.startPlayer(fromURI: url);
return true;
} catch (e) {
print(e);
await _resetPlayerStatus();
return false;
}
}
Future<bool> pauseLocalPlayer() async {
if(!(isLocalPlaying==true && isLocalPausePlaying==false)) {
return false;
}
try {
await localPlayer!.pausePlayer();
return true;
} catch (e) {
print(e);
await _resetPlayerStatus();
return false;
}
}
Future<bool> resumeLocalPlayer() async {
if(!(isLocalPlaying==true && isLocalPausePlaying==true)) {
return false;
}
try {
await localPlayer!.resumePlayer();
return true;
} catch (e) {
print(e);
await _resetPlayerStatus();
return false;
}
}
Future<bool> stopLocalPlayer() async {
try {
localPlayer!.stopPlayer();
return true;
} catch (e) {
print(e);
return false;
} finally {
await _resetPlayerStatus();
}
}
void closeLocalPlayer() async {
await _resetPlayerStatus();
}
Future<void> _resetPlayerStatus() async {
try {
await localPlayer?.closePlayer();
} catch (e) {
print(e);
}
localPlayer = null;
isLocalPlaying = false;
isLocalPausePlaying = false;
localPlayedFilePath = null;
}
// class AudioPlayHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await startLocalPlayer(params['url']);
// return result;
// }
// }
//
// class AudioPauseHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await pauseLocalPlayer();
// return result;
// }
// }
//
// class AudioResumeHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await resumeLocalPlayer();
// return result;
// }
// }
//
// class AudioStopHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await stopLocalPlayer();
// return result;
// }
// }
//
// class AudioClearHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// closeLocalPlayer();
// return true;
// }
// }
import 'package:appframe/services/dispatcher.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
/*录音相关全局参数*/
FlutterSoundRecorder? localRecorder;
bool isLocalRecording = false;
bool isLocalPauseRecording = false;
String? localRecordedFilePath;
Future<bool> initLocalRecorder() async {
// 请求麦克风权限
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('no auth');
}
if (!(!isLocalRecording && !isLocalPauseRecording && localRecordedFilePath == null)) {
return false;
}
// 打开录音器
FlutterSoundRecorder recorder = FlutterSoundRecorder();
try {
localRecorder = await recorder.openRecorder();
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
throw Exception('打开录音器失败!');
}
}
Future<bool> startLocalRecording() async {
if (isLocalRecording) {
return false;
}
if (!await initLocalRecorder()) {
return false;
}
try {
// 获取应用文档目录路径
final directory = await getApplicationDocumentsDirectory();
String filePath = '${directory.path}/bxe_recording.aac';
await localRecorder!.startRecorder(toFile: filePath, codec: Codec.aacMP4);
isLocalRecording = true;
isLocalPauseRecording = false;
localRecordedFilePath = filePath;
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
return false;
}
}
Future<bool> pauseLocalRecording() async {
if (!(isLocalRecording && !isLocalPauseRecording)) {
return false;
}
try {
await localRecorder!.pauseRecorder();
isLocalPauseRecording = true;
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
return false;
}
}
Future<bool> resumeLocalRecording() async {
if (!(isLocalRecording && isLocalPauseRecording)) {
return false;
}
try {
await localRecorder!.resumeRecorder();
isLocalPauseRecording = false;
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
return false;
}
}
Future<String?> stopLocalRecording() async {
if (!(localRecorder != null && isLocalRecording)) {
throw Exception("录音器未初始化");
}
try {
String? url = await localRecorder!.stopRecorder();
isLocalRecording = false;
isLocalPauseRecording = false;
if (url == null || url == "") {
throw Exception("录音失败");
}
await _resetRecorderStatus();
return url;
} catch (e) {
print(e);
await _resetRecorderStatus();
throw Exception("录音失败");
}
}
/// 关闭录音器,释放资源
void closeLocalRecorder() async {
await _resetRecorderStatus();
}
Future<void> _resetRecorderStatus() async {
try {
await localRecorder?.closeRecorder();
} catch (e) {
print(e);
}
localRecorder == null;
isLocalRecording = false;
isLocalPauseRecording = false;
localRecordedFilePath = null;
}
// class AudioRecorderStartHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await startLocalRecording();
// return result;
// }
// }
//
// class AudioRecorderPauseHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await pauseLocalRecording();
// return result;
// }
// }
//
// class AudioRecorderResumeHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await resumeLocalRecording();
// return result;
// }
// }
//
// class AudioRecorderStopHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// return await stopLocalRecording();
// }
// }
//
// class AudioRecorderClearHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// closeLocalRecorder();
// return true;
// }
// }
import 'dart:async';
import 'package:appframe/services/dispatcher.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:flutter/material.dart';
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
class ScanCodeHandler extends MessageHandler {
late WebCubit? _webCubit;
......@@ -12,7 +10,7 @@ class ScanCodeHandler extends MessageHandler {
Future<dynamic> handleMessage(params) async {
try {
final result = await _webCubit!.goScanCode();
// 返回扫码结果
return result;
} finally {
......@@ -28,4 +26,4 @@ class ScanCodeHandler extends MessageHandler {
void _unfollowCubit() {
this._webCubit = null;
}
}
\ No newline at end of file
}
......@@ -15,13 +15,17 @@ class SetTitleHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final String title = params['title'] as String;
final bool showBack = params['showBack'] as bool;
final String title = params['title'] as String;
final bool showBack = params['showBack'] as bool;
return _webCubit!.setTitle(title, showBack);
return _webCubit!.setTitle(title, showBack);
} finally {
_unfollowCubit();
}
}
}
......@@ -16,7 +16,14 @@ class UpgradeHandler extends MessageHandler {
final version = params['version'] as String;
// 1 下载
var direct = await getExternalStorageDirectory();
Directory? direct;
if (Platform.isAndroid) {
direct = await getExternalStorageDirectory();
} else if (Platform.isIOS || Platform.isMacOS) {
direct = await getApplicationDocumentsDirectory();
} else {
throw Exception('未知平台');
}
var saveFilePath = '${direct?.path}/dist_$version.zip';
var dio = Dio();
......
......@@ -21,6 +21,10 @@ class WindowInfoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
_webCubit!.setWindowInfoCmdFlag(true, _message!);
try {
_webCubit!.setWindowInfoCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
import 'package:appframe/config/db.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/wechat.dart';
import 'package:appframe/config/mqtt.dart';
import 'package:flutter/material.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await registerWechatApi();
await setupLocator();
await registerMqtt();
await initDatabase();
runApp(const App());
}
......@@ -144,10 +144,13 @@ class LocalServerService {
}
Future<void> _initHttpDirectory() async {
var direct = await getExternalStorageDirectory();
_httpDirectory = '${direct?.path}/http_dist_assets';
// var direct = await getApplicationSupportDirectory();
// _httpDirectory = '${direct.path}/http_dist_assets';
if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory();
_httpDirectory = '${direct?.path}/http_dist_assets';
} else if (Platform.isIOS || Platform.isMacOS) {
var direct = await getApplicationSupportDirectory();
_httpDirectory = '${direct.path}/http_dist_assets';
}
}
Future<void> _extractDist() async {
......@@ -190,19 +193,17 @@ class LocalServerService {
var distUrl = "https://github.com/xinxin-wu/flutter_web_dist/releases/download/v1.0.0/dist.zip";
// Dio进行下载
var dio = Dio();
dio
.download(
distUrl,
'$httpDirectory/dist.zip',
onReceiveProgress: (received, total) {
if (total != -1) {
print((received / total * 100).toStringAsFixed(0) + "%");
}
},
)
.then((_) {
_extractDist();
});
dio.download(
distUrl,
'$httpDirectory/dist.zip',
onReceiveProgress: (received, total) {
if (total != -1) {
print((received / total * 100).toStringAsFixed(0) + "%");
}
},
).then((_) {
_extractDist();
});
dio.close();
}
}
import 'dart:async';
import 'dart:io';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
class MqttService {
/// MQTT服务器信息
final String _host;
final int _port;
final String _clientId;
final int _keepAlive;
final String _username;
/// 重连的时候要重新获取一下 token
final String _password;
/// MQTT客户端
late MqttServerClient _client;
StreamSubscription? _subscription;
/// MQTT重连相关变量
int _reconnectAttempts = 0;
final int _maxReconnectAttempts = 5;
Timer? _reconnectTimer;
MqttService(this._host, this._port, this._clientId, this._keepAlive, this._username, this._password);
Future<void> initConn() async {
_client = MqttServerClient(_host, _clientId);
_client.logging(on: false);
_client.port = _port;
_client.keepAlivePeriod = _keepAlive;
_client.onDisconnected = onDisconnected;
_client.onConnected = onConnected;
_client.onSubscribed = onSubscribed;
_client.pongCallback = pong;
final connMess = MqttConnectMessage()
.withWillTopic('willtopic')
.withWillMessage('My Will message')
.startClean()
.withWillQos(MqttQos.atLeastOnce);
_client.connectionMessage = connMess;
try {
print('MQTT 开始连接......');
await _client.connect(_username, _password);
} on NoConnectionException catch (e) {
print('MQTT客户端连接失败: $e');
_client.disconnect();
} on SocketException catch (e) {
print('MQTT客户端连接失败: $e');
_client.disconnect();
}
// 订阅主题
subscribe("\$q/bxe/abc");
// 监听消息
listen(onMessageReceived);
}
void onConnected() {
print('MQTT客户端连接成功');
_reconnectAttempts = 0;
_reconnectTimer?.cancel();
}
void onDisconnected() {
print('MQTT客户端断开连接');
// if (_reconnectAttempts < _maxReconnectAttempts) {
// handleReconnect(_host, _port, _clientId, '');
// }
}
void onSubscribed(String topic) {
print('MQTT客户端订阅主题: $topic');
}
void onMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('MQTT消息接收: topic is ${c[0].topic}, payload is $pt');
// 处理接收到的消息
}
void subscribe(String topic) {
_client.subscribe(topic, MqttQos.atLeastOnce);
}
void listen(Function(List<MqttReceivedMessage<MqttMessage?>>? c) onMessageReceivedCallback) {
_subscription?.cancel();
_subscription = _client.updates?.listen(onMessageReceivedCallback);
}
void disconnect() {
_client.disconnect();
}
bool isConnected() {
return _client.connectionStatus?.state == MqttConnectionState.connected;
}
void pong() {
print('Ping response client callback invoked');
}
void onMqttMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('MQTT消息接收: topic is ${c[0].topic}, payload is $pt');
// 处理接收到的消息
}
void handleReconnect(String host, int port, String username, String password) {
if (_reconnectAttempts >= _maxReconnectAttempts) {
print('已达到最大重连次数,停止重连');
return;
}
_reconnectAttempts++;
print('第 $_reconnectAttempts 次重连尝试,${_maxReconnectAttempts - _reconnectAttempts} 次机会剩余');
// 取消之前的重连定时器(如果有)
_reconnectTimer?.cancel();
// 设置延迟重连,每次重连间隔递增
final delay = Duration(seconds: _reconnectAttempts * 5);
_reconnectTimer = Timer(delay, () async {
print('正在尝试重新连接...');
await initConn();
if (!isConnected()) {
disconnect();
}
});
}
}
import 'package:appframe/bloc/mqtt_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MqttPage extends StatelessWidget {
const MqttPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => MqttCubit(),
child: BlocConsumer<MqttCubit, MqttState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text('推送测试'),
centerTitle: true,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
context.read<MqttCubit>().goWeb();
},
),
),
body: ListView(
children: state.messages
.map((message) => ListTile(
title: Text(message),
))
.toList(),
));
},
listener: (context, state) {},
));
}
}
......@@ -148,6 +148,14 @@ class WebPage extends StatelessWidget {
),
),
ListTile(
leading: const Icon(Icons.chat_bubble_outline),
title: const Text('推送测试'),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().goMqtt();
},
),
ListTile(
leading: const Icon(Icons.login),
title: const Text('登录界面'),
onTap: () {
......
......@@ -297,6 +297,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.7"
event_bus:
dependency: transitive
description:
name: event_bus
sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
exif:
dependency: "direct main"
description:
......@@ -813,6 +821,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.1.2"
mqtt_client:
dependency: "direct main"
description:
name: mqtt_client
sha256: "3b3f406bf804df7de909f2c5098fba1a7d87fc4d86f01b6a12dc8d1b24be221f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "10.11.1"
nested:
dependency: transitive
description:
......@@ -1242,6 +1258,46 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
sqflite:
dependency: "direct main"
description:
name: sqflite
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2+2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.6"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.2"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
stack_trace:
dependency: transitive
description:
......
......@@ -62,12 +62,14 @@ dependencies:
json_annotation: ^4.9.0
mime: ^2.0.0
mobile_scanner: ^7.0.1
mqtt_client: ^10.11.1
network_info_plus: ^7.0.0
open_file: ^3.5.10
path: ^1.9.1
path_provider: ^2.1.5
permission_handler: ^12.0.1
shared_preferences: ^2.5.3
sqflite: ^2.4.2
# video_thumbnail: ^0.5.6
url_launcher: ^6.3.2
uuid: ^4.5.1
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!