Commit 128403df by tanghuan

dev

1 parent 59970d1c
......@@ -15,6 +15,9 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<application
android:label="班小二"
......
......@@ -5,9 +5,11 @@
<domain includeSubdomains="false">127.0.0.1</domain>
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">192.168.2.59</domain>
<domain includeSubdomains="false">192.168.2.215</domain>
<domain includeSubdomains="false">192.168.2.177</domain>
<domain includeSubdomains="false">192.168.1.136</domain>
<domain includeSubdomains="false">localdev.banxiaoer.net</domain>
<domain includeSubdomains="false">appdev-th.banxiaoer.net</domain>
<domain includeSubdomains="false">appdev-xj.banxiaoer.net</domain>
</domain-config>
</network-security-config>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>登录跳转</h2>
</body>
<script>
window.onload = function () {
let message = '{ "timestamp": 1, "unique": "1", "cmd": "goLogin", "params": {} }';
xeJsBridge.postMessage(message);
}
</script>
</html>
\ No newline at end of file
......@@ -9,6 +9,15 @@
<div id="resp"></div>
<input type="file" />
<audio controls>
<source src="https://files-obs.banxiaoer.com/d2/pridel/user/20250918/bxe/220387141757177856/f/comm/bxe_homework/audio/fmp3t1758184802425n5zldhtvw.mp3">
</audio>
<img id="testImg" src="" alt="">
<button onclick="clearResp()">清除响应数据</button>
<br>
......@@ -26,7 +35,7 @@
<br>
<br>
<button onclick="chooseImage(1)">选择单个图片</button>
<button onclick="chooseMultipleImage()">选择多个图片</button>
<button onclick="chooseMultipleImage(1)">选择多个图片</button>
<br>
<br>
......@@ -55,6 +64,8 @@
<script src="/test/test.js"></script>
......
......@@ -11,6 +11,12 @@ function clearResp() {
// 接收Flutter响应数据
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
/*let jsonObj=JSON.parse(message);
if(jsonObj.cmd=='chooseImage'){
document.getElementById('testImg').src='/temp'+jsonObj.data[0].tempFilePath;
}*/
}
// 测试获取设备信息
......@@ -52,11 +58,11 @@ function chooseImage(sourceType) {
}
function chooseMultipleImage() {
function chooseMultipleImage(sourceType) {
let params = {
"timestamp": 1, "unique": "123", "cmd": "chooseImage", "params": {
"sourceType": "album",
"sourceType": sourceType == 1 ? "album" : "camera",
"count": 9,
"sizeType": ["original", "compressed"],
}
......
......@@ -36,7 +36,7 @@
cmd: 'chooseFile',
params: {
count: 2,
fileTypes: ['docx', 'xlsx', 'pdf', 'mp4','csv']
fileTypes: ['docx', 'xlsx', 'pdf', 'mp4','csv', 'png', 'm4a']
}
};
xeJsBridge.postMessage(JSON.stringify(params));
......
......@@ -16,6 +16,7 @@ import 'package:fluwx/fluwx.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
......@@ -55,6 +56,10 @@ class WebState extends Equatable {
final bool chooseImageCmdFlag;
final String chooseImageCmdMessage;
/// chooseVideoCmd
final bool chooseVideoCmdFlag;
final String chooseVideoCmdMessage;
const WebState({
this.loaded = false,
this.title = '界面加载中...',
......@@ -77,6 +82,8 @@ class WebState extends Equatable {
this.windowInfoCmdMessage = '',
this.chooseImageCmdFlag = false,
this.chooseImageCmdMessage = '',
this.chooseVideoCmdFlag = false,
this.chooseVideoCmdMessage = '',
});
WebState copyWith({
......@@ -101,6 +108,8 @@ class WebState extends Equatable {
String? windowInfoCmdMessage,
bool? chooseImageCmdFlag,
String? chooseImageCmdMessage,
bool? chooseVideoCmdFlag,
String? chooseVideoCmdMessage,
}) {
return WebState(
loaded: loaded ?? this.loaded,
......@@ -124,6 +133,8 @@ class WebState extends Equatable {
windowInfoCmdMessage: windowInfoCmdMessage ?? this.windowInfoCmdMessage,
chooseImageCmdFlag: chooseImageCmdFlag ?? this.chooseImageCmdFlag,
chooseImageCmdMessage: chooseImageCmdMessage ?? this.chooseImageCmdMessage,
chooseVideoCmdFlag: chooseVideoCmdFlag ?? this.chooseVideoCmdFlag,
chooseVideoCmdMessage: chooseVideoCmdMessage ?? this.chooseVideoCmdMessage,
);
}
......@@ -150,6 +161,8 @@ class WebState extends Equatable {
windowInfoCmdMessage,
chooseImageCmdFlag,
chooseImageCmdMessage,
chooseVideoCmdFlag,
chooseVideoCmdMessage,
];
}
......@@ -211,13 +224,11 @@ class WebCubit extends Cubit<WebState> {
final String serverUrl;
if (state.sessionCode == null || state.sessionCode == '') {
// serverUrl = 'http://${state.ip}:${_server.port}/test/test.html';
serverUrl = 'http://127.0.0.1:${_server.port}/test/test.html';
serverUrl = 'http://127.0.0.1:${_server.port}/test/login.html';
// serverUrl = 'http://127.0.0.1:${_server.port}/test/test.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://localdev.banxiaoer.net/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));
......@@ -257,7 +268,8 @@ class WebCubit extends Cubit<WebState> {
//测试
void goAuth() {
String serverUrl = 'http://${state.ip}:${_server.port}/index.html';
// String serverUrl = 'http://${state.ip}:${_server.port}/index.html';
String serverUrl = 'http://127.0.0.1:${_server.port}/index.html';
// String serverUrl = 'http://localdev.banxiaoer.net';
_controller.loadRequest(Uri.parse(serverUrl));
}
......@@ -303,6 +315,10 @@ class WebCubit extends Cubit<WebState> {
_controller.reload();
}
Future<void> clearStorage() async {
await getIt.get<SharedPreferences>().clear();
}
void setChooseImageCmdFlag(bool chooseImageCmdFlag, String chooseImageCmdMessage) {
emit(state.copyWith(chooseImageCmdFlag: chooseImageCmdFlag, chooseImageCmdMessage: chooseImageCmdMessage));
}
......@@ -333,22 +349,27 @@ class WebCubit extends Cubit<WebState> {
// 相册
if (sourceType == 'album') {
_chooseFromAlbum(context, count, h5Message.unique, h5Message.cmd);
_chooseImageFromAlbum(context, count, h5Message.unique, h5Message.cmd);
}
// 拍照
else {
_chooseFromCamera(context, h5Message.unique, h5Message.cmd);
_chooseImageFromCamera(context, h5Message.unique, h5Message.cmd);
}
}
void _chooseFromAlbum(BuildContext context, int count, String unique, String cmd) async {
void _chooseImageFromAlbum(BuildContext context, int count, String unique, String cmd) async {
final List<AssetEntity>? result = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(maxAssets: count, requestType: RequestType.image),
pickerConfig: AssetPickerConfig(
maxAssets: count,
requestType: RequestType.image,
gridThumbnailSize: const ThumbnailSize.square(80),
previewThumbnailSize: const ThumbnailSize.square(150),
),
);
if (result == null || result.isEmpty) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': '未选择图片'};
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'cancel'};
_sendResponse(resp);
return;
}
......@@ -360,15 +381,20 @@ class WebCubit extends Cubit<WebState> {
for (var asset in result) {
resultList.add(await _handleSingleImage(asset, tempDir));
}
var resp = {'unique': unique, 'cmd': cmd, 'data': resultList, 'errMsg': ''};
var resp = {
'unique': unique,
'cmd': cmd,
'data': {'tempFiles': resultList},
'errMsg': '',
};
_sendResponse(resp);
}
void _chooseFromCamera(BuildContext context, String unique, String cmd) async {
void _chooseImageFromCamera(BuildContext context, String unique, String cmd) async {
AssetEntity? asset = await CameraPicker.pickFromCamera(context, pickerConfig: const CameraPickerConfig());
if (asset == null) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': '未选择图片'};
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'cancel'};
_sendResponse(resp);
return;
}
......@@ -378,7 +404,9 @@ class WebCubit extends Cubit<WebState> {
var resp = {
'unique': unique,
'cmd': cmd,
'data': [result],
'data': {
'tempFiles': [result],
},
'errMsg': '',
};
_sendResponse(resp);
......@@ -394,11 +422,132 @@ class WebCubit extends Cubit<WebState> {
).writeAsBytes(data!);
return {
"tempFilePath": file!.path,
"tempFilePath": 'http://127.0.0.1:${_server.port}/temp${file!.path}',
"size": file.lengthSync(),
"width": asset.width,
"height": asset.height,
"thumbTempFilePath": 'http://127.0.0.1:${_server.port}/temp${thumbnailFile.path}',
"fileType": file.path.split('/').last.split('.').last,
};
}
void setChooseVideoCmdFlag(bool chooseVideoCmdFlag, String chooseVideoCmdMessage) {
emit(state.copyWith(chooseVideoCmdFlag: chooseVideoCmdFlag, chooseVideoCmdMessage: chooseVideoCmdMessage));
}
void chooseVideo(BuildContext context) async {
final Map<String, dynamic> data = json.decode(state.chooseVideoCmdMessage);
H5Message h5Message = H5Message.fromJson(data);
setChooseVideoCmdFlag(false, '');
final params = h5Message.params;
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
var sourceType = params['sourceType'] as String;
if (sourceType != 'album' && sourceType != 'camera') {
sourceType = 'album';
}
// 暂时忽略 sizeType 参数
int count = 1;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
count = 9;
}
}
int maxDuration = 60;
if (params.containsKey('maxDuration')) {
maxDuration = params['maxDuration'] as int;
if (maxDuration < 1 || maxDuration > 600) {
maxDuration = 60;
}
}
// 相册选择
if (sourceType == 'album') {
_chooseVideoFromAlbum(context, count, h5Message.unique, h5Message.cmd);
}
// 拍摄
else {
_chooseVideoFromCamera(context, maxDuration, h5Message.unique, h5Message.cmd);
}
}
void _chooseVideoFromAlbum(BuildContext context, int count, String unique, String cmd) async {
final List<AssetEntity>? result = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(maxAssets: count, requestType: RequestType.video),
);
if (result == null || result.isEmpty) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'cancel'};
_sendResponse(resp);
return;
}
// 获取临时目录
final Directory tempDir = await getTemporaryDirectory();
final List<Map<String, dynamic>> resultList = [];
for (var asset in result) {
resultList.add(await _handleSingleVideo(asset, tempDir));
}
var resp = {
'unique': unique,
'cmd': cmd,
'data': {'tempFiles': resultList},
'errMsg': '',
};
_sendResponse(resp);
}
void _chooseVideoFromCamera(BuildContext context, int maxDuration, String unique, String cmd) async {
AssetEntity? asset = await CameraPicker.pickFromCamera(
context,
pickerConfig: CameraPickerConfig(
enableRecording: true,
onlyEnableRecording: true,
maximumRecordingDuration: Duration(seconds: maxDuration),
),
);
if (asset == null) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'cancel'};
_sendResponse(resp);
return;
}
final Directory tempDir = await getTemporaryDirectory();
final Map<String, dynamic> result = await _handleSingleVideo(asset, tempDir);
var resp = {
'unique': unique,
'cmd': cmd,
'data': {
'tempFiles': [result],
},
'errMsg': '',
};
_sendResponse(resp);
}
Future<Map<String, dynamic>> _handleSingleVideo(AssetEntity asset, Directory tempDir) async {
final file = await asset.file;
// 获取缩略图
final data = await asset.thumbnailData;
final thumbnailFile = await File(
'${tempDir.path}/${DateTime.now().millisecondsSinceEpoch}.png',
).writeAsBytes(data!);
return {
"tempFilePath": 'http://127.0.0.1:${_server.port}/temp${file!.path}',
"size": file.lengthSync(),
"width": asset.width,
"height": asset.height,
"thumbTempFilePath": '/temp${thumbnailFile.path}',
"thumbTempFilePath": 'http://127.0.0.1:${_server.port}/temp${thumbnailFile.path}',
"fileType": file.path.split('/').last.split('.').last,
};
}
......@@ -475,7 +624,7 @@ class WebCubit extends Cubit<WebState> {
// 请求麦克风权限
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('麦克风权限未授权!');
throw RecordingPermissionException('no auth');
}
if (state.recordState != 0) {
......
......@@ -5,10 +5,12 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WechatAuthState extends Equatable {
final String ip;
final String? result;
final String? sessionCode;
final String? userCode;
final String? classCode;
......@@ -61,8 +63,8 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
_fluwx.addSubscriber(_responseListener);
// _textEditingController = TextEditingController()..text = '127.0.0.1';
_textEditingController = TextEditingController()..text = 'appdev-th.banxiaoer.net';
// _textEditingController = TextEditingController()..text = 'appdev-xj.banxiaoer.net';
// _textEditingController = TextEditingController()..text = 'appdev-th.banxiaoer.net';
_textEditingController = TextEditingController()..text = 'appdev-xj.banxiaoer.net';
// _textEditingController = TextEditingController()..text = '192.168.1.136';
_wechatAuthRepository = getIt<WechatAuthRepository>();
......@@ -75,15 +77,31 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
var data = resultData['data'];
var role = data['roles'][0];
final sessionCode = data['sessionCode'];
final userCode = data['userCode'];
final classCode = role['classCode'];
final userType = role['userType'];
final stuId = role['stuId'];
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId??'');
// sharedPreferences.setString('auth_ip', 'appdev-th.banxiaoer.net');
sharedPreferences.setString('auth_ip', 'appdev-xj.banxiaoer.net');
// sharedPreferences.setString('auth_ip', '192.168.1.136');
router.go(
'/web',
extra: {
'ip': state.ip,
'sessionCode': data['sessionCode'],
'userCode': data['userCode'],
'classCode': role['classCode'],
'userType': role['userType'],
'stuId': role['stuId'],
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
}
......
......@@ -8,6 +8,7 @@ import 'package:appframe/data/repositories/message/clipboard_data_handler.dart';
import 'package:appframe/data/repositories/message/compress_handler.dart';
import 'package:appframe/data/repositories/message/device_info_handler.dart';
import 'package:appframe/data/repositories/message/download_file_handler.dart';
import 'package:appframe/data/repositories/message/go_login_handler.dart';
import 'package:appframe/data/repositories/message/image_info_handler.dart';
import 'package:appframe/data/repositories/message/location_handler.dart';
import 'package:appframe/data/repositories/message/network_type_handler.dart';
......@@ -154,6 +155,9 @@ Future<void> setupLocator() async {
/// 设置标题和返回按钮
getIt.registerLazySingleton<MessageHandler>(() => SetTitleHandler(), instanceName: 'setTitle');
/// 登录
getIt.registerLazySingleton<MessageHandler>(() => GoLoginHandler(), instanceName: 'goLogin');
/// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net'));
......
final int serverPort = 35982;
import 'dart:io';
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/thumbnail_util.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_size_getter/file_input.dart';
import 'package:image_size_getter/image_size_getter.dart';
import 'package:path_provider/path_provider.dart';
class ChooseVideoHandler extends MessageHandler {
late WebCubit? _webCubit;
late String? _message;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
void setMessage(String message) {
this._message = message;
}
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
......@@ -17,14 +28,6 @@ class ChooseVideoHandler extends MessageHandler {
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')) {
......@@ -34,104 +37,143 @@ class ChooseVideoHandler extends MessageHandler {
}
}
int number = 60;
if (params.containsKey('number')) {
number = params['number'] as int;
if (number < 1 || number > 60) {
int maxDuration = 60;
if (params.containsKey('maxDuration')) {
maxDuration = params['maxDuration'] as int;
if (maxDuration < 1 || maxDuration > 600) {
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,
};
_webCubit!.setChooseVideoCmdFlag(true, _message!);
}
}
// 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,
// };
// }
// }
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
class GoLoginHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(params) async {
_webCubit!.goWechatAuth();
return;
}
}
......@@ -23,12 +23,12 @@ class LocationHandler extends MessageHandler {
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
throw '定位权限被拒绝';
throw 'no auth';
}
}
if (permission == LocationPermission.deniedForever) {
throw '定位权限被永久拒绝';
throw 'no auth';
}
final pos = await geolocator.getCurrentPosition();
......
......@@ -13,7 +13,7 @@ Future<bool> initLocalRecorder() async {
// 请求麦克风权限
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('麦克风权限未授权!');
throw RecordingPermissionException('no auth');
}
if (!(!isLocalRecording && !isLocalPauseRecording && localRecordedFilePath == null)) {
......
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:archive/archive.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
class UpgradeHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
final version = params['version'] as String;
// 1 下载
var direct = await getExternalStorageDirectory();
var saveFilePath = '${direct?.path}/dist_$version.zip';
var dio = Dio();
final resp = await dio.download(
url,
saveFilePath,
onReceiveProgress: (received, total) {
if (total != -1) {
print((received / total * 100).toStringAsFixed(0) + "%");
}
},
);
dio.close();
if (resp.statusCode != 200) {
throw Exception('文件下载失败');
}
// 2 解压
var outputDirectory = '${direct?.path}/http_dist_assets';
await _extract(saveFilePath, outputDirectory);
}
Future<void> _extract(String zipFilePath, String outputDirectory) async {
var file = File(zipFilePath);
var bytes = await file.readAsBytes();
// 解码 ZIP 文件
final archive = ZipDecoder().decodeBytes(bytes);
// 提取文件到指定目录
for (final file in archive) {
final filename = file.name;
if (file.isFile) {
final data = file.content as List<int>;
final outputFile = File('$outputDirectory/$filename');
outputFile.createSync(recursive: true);
outputFile.writeAsBytesSync(data);
} else {
// 创建目录
Directory('$outputDirectory/$filename').createSync(recursive: true);
}
}
}
}
......@@ -52,6 +52,8 @@ class MessageDispatcher {
h5Message.cmd == "getOrientation" ||
h5Message.cmd == "getWindowInfo" ||
h5Message.cmd == "chooseImage" ||
h5Message.cmd == "chooseVideo" ||
h5Message.cmd == "goLogin" ||
h5Message.cmd.startsWith("audio") ||
h5Message.cmd.startsWith("setTitle")) {
handler.setCubit(webCubit!);
......
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
class LocalServerService {
String? _httpDirectory;
// 启动本地HTTP服务器
Future<HttpServer> startLocalServer() async {
// 测试情况下, 每次启动服务,先解压dist文件
_extractDist();
HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 35982);
// HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8080);
print('本地服务器启动在端口: ${server.port}');
server.listen((HttpRequest request) async {
......@@ -15,14 +21,14 @@ class LocalServerService {
try {
if (requestPath.startsWith('/temp/')) {
// 临时目录文件的请求
// 目录文件服务逻辑
await _serveTempFile(request, requestPath);
} else if (requestPath.startsWith('/test/')) {
// assets文件服务逻辑
// 内部assets文件服务逻辑
await _serveAssetFile(request, requestPath);
} else {
// asset/dist.zip 文件服务逻辑
await _serveZipFileContent(request, requestPath);
// 内部集成H5文件服务逻辑
await _serveHttpFile(request, requestPath);
}
} catch (e) {
print('处理请求时出错: $e');
......@@ -36,10 +42,10 @@ class LocalServerService {
return server;
}
// 临时目录的文件
// 目录下的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try {
// 临时文件已经设备路径
// 临时文件路径
// 构建文件路径(移除 /temp 前缀)
final String filePath = requestPath.substring('/temp/'.length);
......@@ -68,52 +74,32 @@ class LocalServerService {
}
}
Future<void> _serveZipFileContent(HttpRequest request, String requestPath) async {
String zipAssetPath = 'assets/dist.zip';
Future<void> _serveHttpFile(HttpRequest request, String requestPath) async {
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('\\', '/');
var httpDirectory = await getHttpDirectory();
final String filePath = '$httpDirectory$requestPath';
// 移除开头的 '/'(如果存在)
if (requestPath.startsWith('/')) {
requestPath = requestPath.substring(1);
}
if (zipFileName == requestPath) {
targetFile = file;
break;
}
}
// 检查文件是否存在
final File file = File(filePath);
if (await file.exists()) {
// 读取文件内容
final List<int> bytes = await file.readAsBytes();
if (targetFile == null) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found in zip: $requestPath')
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
return;
}
// 返回文件内容
} else {
request.response
..headers.contentType = ContentType.parse(_getContentType(requestPath))
..add(targetFile.content as List<int>)
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
} catch (e) {
print('读取zip文件时出错: $e');
print('读取临时文件时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Error reading zip file')
..statusCode = HttpStatus.notFound
..write('File not found')
..close();
}
}
......@@ -148,4 +134,68 @@ class LocalServerService {
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
return 'application/octet-stream';
}
Future<String> getHttpDirectory() async {
if (_httpDirectory == null) {
await _initHttpDirectory();
}
return _httpDirectory!;
}
Future<void> _initHttpDirectory() async {
var direct = await getExternalStorageDirectory();
_httpDirectory = '${direct?.path}/http_dist_assets';
// var direct = await getApplicationSupportDirectory();
// _httpDirectory = '${direct.path}/http_dist_assets';
}
Future<void> _extractDist() async {
var outputDirectory = await getHttpDirectory();
// 判断目录存在则不需要再解压
if (Directory(outputDirectory).existsSync()) {
return;
}
var zipFilePath = "assets/dist.zip";
final ByteData data = await rootBundle.load(zipFilePath);
final bytes = data.buffer.asUint8List();
// 解码 ZIP 文件
final archive = ZipDecoder().decodeBytes(bytes);
// 提取文件到指定目录
for (final file in archive) {
final filename = file.name;
if (file.isFile) {
final data = file.content as List<int>;
final outputFile = File('$outputDirectory/$filename');
outputFile.createSync(recursive: true);
outputFile.writeAsBytesSync(data);
} else {
// 创建目录
Directory('$outputDirectory/$filename').createSync(recursive: true);
}
}
}
Future<void> _clearDist() async {
var outputDirectory = await getHttpDirectory();
Directory(outputDirectory).deleteSync(recursive: true);
}
void _downloadDist() async {
var httpDirectory = await getHttpDirectory();
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.close();
}
}
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!