Commit 128403df by tanghuan

dev

1 parent 59970d1c
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_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 <application
android:label="班小二" android:label="班小二"
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
<domain includeSubdomains="false">127.0.0.1</domain> <domain includeSubdomains="false">127.0.0.1</domain>
<domain includeSubdomains="false">localhost</domain> <domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">192.168.2.59</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.2.177</domain>
<domain includeSubdomains="false">192.168.1.136</domain> <domain includeSubdomains="false">192.168.1.136</domain>
<domain includeSubdomains="false">localdev.banxiaoer.net</domain> <domain includeSubdomains="false">localdev.banxiaoer.net</domain>
<domain includeSubdomains="false">appdev-th.banxiaoer.net</domain> <domain includeSubdomains="false">appdev-th.banxiaoer.net</domain>
<domain includeSubdomains="false">appdev-xj.banxiaoer.net</domain>
</domain-config> </domain-config>
</network-security-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 \ No newline at end of file
...@@ -9,6 +9,15 @@ ...@@ -9,6 +9,15 @@
<div id="resp"></div> <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> <button onclick="clearResp()">清除响应数据</button>
<br> <br>
...@@ -26,7 +35,7 @@ ...@@ -26,7 +35,7 @@
<br> <br>
<br> <br>
<button onclick="chooseImage(1)">选择单个图片</button> <button onclick="chooseImage(1)">选择单个图片</button>
<button onclick="chooseMultipleImage()">选择多个图片</button> <button onclick="chooseMultipleImage(1)">选择多个图片</button>
<br> <br>
<br> <br>
...@@ -55,6 +64,8 @@ ...@@ -55,6 +64,8 @@
<script src="/test/test.js"></script> <script src="/test/test.js"></script>
......
...@@ -11,6 +11,12 @@ function clearResp() { ...@@ -11,6 +11,12 @@ function clearResp() {
// 接收Flutter响应数据 // 接收Flutter响应数据
function xeJsBridgeCallback(message) { function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>'; 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) { ...@@ -52,11 +58,11 @@ function chooseImage(sourceType) {
} }
function chooseMultipleImage() { function chooseMultipleImage(sourceType) {
let params = { let params = {
"timestamp": 1, "unique": "123", "cmd": "chooseImage", "params": { "timestamp": 1, "unique": "123", "cmd": "chooseImage", "params": {
"sourceType": "album", "sourceType": sourceType == 1 ? "album" : "camera",
"count": 9, "count": 9,
"sizeType": ["original", "compressed"], "sizeType": ["original", "compressed"],
} }
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
cmd: 'chooseFile', cmd: 'chooseFile',
params: { params: {
count: 2, count: 2,
fileTypes: ['docx', 'xlsx', 'pdf', 'mp4','csv'] fileTypes: ['docx', 'xlsx', 'pdf', 'mp4','csv', 'png', 'm4a']
} }
}; };
xeJsBridge.postMessage(JSON.stringify(params)); xeJsBridge.postMessage(JSON.stringify(params));
......
...@@ -16,6 +16,7 @@ import 'package:fluwx/fluwx.dart'; ...@@ -16,6 +16,7 @@ import 'package:fluwx/fluwx.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart';
...@@ -55,6 +56,10 @@ class WebState extends Equatable { ...@@ -55,6 +56,10 @@ class WebState extends Equatable {
final bool chooseImageCmdFlag; final bool chooseImageCmdFlag;
final String chooseImageCmdMessage; final String chooseImageCmdMessage;
/// chooseVideoCmd
final bool chooseVideoCmdFlag;
final String chooseVideoCmdMessage;
const WebState({ const WebState({
this.loaded = false, this.loaded = false,
this.title = '界面加载中...', this.title = '界面加载中...',
...@@ -77,6 +82,8 @@ class WebState extends Equatable { ...@@ -77,6 +82,8 @@ class WebState extends Equatable {
this.windowInfoCmdMessage = '', this.windowInfoCmdMessage = '',
this.chooseImageCmdFlag = false, this.chooseImageCmdFlag = false,
this.chooseImageCmdMessage = '', this.chooseImageCmdMessage = '',
this.chooseVideoCmdFlag = false,
this.chooseVideoCmdMessage = '',
}); });
WebState copyWith({ WebState copyWith({
...@@ -101,6 +108,8 @@ class WebState extends Equatable { ...@@ -101,6 +108,8 @@ class WebState extends Equatable {
String? windowInfoCmdMessage, String? windowInfoCmdMessage,
bool? chooseImageCmdFlag, bool? chooseImageCmdFlag,
String? chooseImageCmdMessage, String? chooseImageCmdMessage,
bool? chooseVideoCmdFlag,
String? chooseVideoCmdMessage,
}) { }) {
return WebState( return WebState(
loaded: loaded ?? this.loaded, loaded: loaded ?? this.loaded,
...@@ -124,6 +133,8 @@ class WebState extends Equatable { ...@@ -124,6 +133,8 @@ class WebState extends Equatable {
windowInfoCmdMessage: windowInfoCmdMessage ?? this.windowInfoCmdMessage, windowInfoCmdMessage: windowInfoCmdMessage ?? this.windowInfoCmdMessage,
chooseImageCmdFlag: chooseImageCmdFlag ?? this.chooseImageCmdFlag, chooseImageCmdFlag: chooseImageCmdFlag ?? this.chooseImageCmdFlag,
chooseImageCmdMessage: chooseImageCmdMessage ?? this.chooseImageCmdMessage, chooseImageCmdMessage: chooseImageCmdMessage ?? this.chooseImageCmdMessage,
chooseVideoCmdFlag: chooseVideoCmdFlag ?? this.chooseVideoCmdFlag,
chooseVideoCmdMessage: chooseVideoCmdMessage ?? this.chooseVideoCmdMessage,
); );
} }
...@@ -150,6 +161,8 @@ class WebState extends Equatable { ...@@ -150,6 +161,8 @@ class WebState extends Equatable {
windowInfoCmdMessage, windowInfoCmdMessage,
chooseImageCmdFlag, chooseImageCmdFlag,
chooseImageCmdMessage, chooseImageCmdMessage,
chooseVideoCmdFlag,
chooseVideoCmdMessage,
]; ];
} }
...@@ -211,13 +224,11 @@ class WebCubit extends Cubit<WebState> { ...@@ -211,13 +224,11 @@ class WebCubit extends Cubit<WebState> {
final String serverUrl; final String serverUrl;
if (state.sessionCode == null || state.sessionCode == '') { if (state.sessionCode == null || state.sessionCode == '') {
// serverUrl = 'http://${state.ip}:${_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'; // serverUrl = 'http://127.0.0.1:${_server.port}/test/test.html';
} else { } else {
serverUrl = 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}'; '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)); _controller.loadRequest(Uri.parse(serverUrl));
...@@ -257,7 +268,8 @@ class WebCubit extends Cubit<WebState> { ...@@ -257,7 +268,8 @@ class WebCubit extends Cubit<WebState> {
//测试 //测试
void goAuth() { 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'; // String serverUrl = 'http://localdev.banxiaoer.net';
_controller.loadRequest(Uri.parse(serverUrl)); _controller.loadRequest(Uri.parse(serverUrl));
} }
...@@ -303,6 +315,10 @@ class WebCubit extends Cubit<WebState> { ...@@ -303,6 +315,10 @@ class WebCubit extends Cubit<WebState> {
_controller.reload(); _controller.reload();
} }
Future<void> clearStorage() async {
await getIt.get<SharedPreferences>().clear();
}
void setChooseImageCmdFlag(bool chooseImageCmdFlag, String chooseImageCmdMessage) { void setChooseImageCmdFlag(bool chooseImageCmdFlag, String chooseImageCmdMessage) {
emit(state.copyWith(chooseImageCmdFlag: chooseImageCmdFlag, chooseImageCmdMessage: chooseImageCmdMessage)); emit(state.copyWith(chooseImageCmdFlag: chooseImageCmdFlag, chooseImageCmdMessage: chooseImageCmdMessage));
} }
...@@ -333,22 +349,27 @@ class WebCubit extends Cubit<WebState> { ...@@ -333,22 +349,27 @@ class WebCubit extends Cubit<WebState> {
// 相册 // 相册
if (sourceType == 'album') { if (sourceType == 'album') {
_chooseFromAlbum(context, count, h5Message.unique, h5Message.cmd); _chooseImageFromAlbum(context, count, h5Message.unique, h5Message.cmd);
} }
// 拍照 // 拍照
else { 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( final List<AssetEntity>? result = await AssetPicker.pickAssets(
context, 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) { 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); _sendResponse(resp);
return; return;
} }
...@@ -360,15 +381,20 @@ class WebCubit extends Cubit<WebState> { ...@@ -360,15 +381,20 @@ class WebCubit extends Cubit<WebState> {
for (var asset in result) { for (var asset in result) {
resultList.add(await _handleSingleImage(asset, tempDir)); 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); _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()); AssetEntity? asset = await CameraPicker.pickFromCamera(context, pickerConfig: const CameraPickerConfig());
if (asset == null) { if (asset == null) {
var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': '未选择图片'}; var resp = {'unique': unique, 'cmd': cmd, 'data': null, 'errMsg': 'cancel'};
_sendResponse(resp); _sendResponse(resp);
return; return;
} }
...@@ -378,7 +404,9 @@ class WebCubit extends Cubit<WebState> { ...@@ -378,7 +404,9 @@ class WebCubit extends Cubit<WebState> {
var resp = { var resp = {
'unique': unique, 'unique': unique,
'cmd': cmd, 'cmd': cmd,
'data': [result], 'data': {
'tempFiles': [result],
},
'errMsg': '', 'errMsg': '',
}; };
_sendResponse(resp); _sendResponse(resp);
...@@ -394,11 +422,132 @@ class WebCubit extends Cubit<WebState> { ...@@ -394,11 +422,132 @@ class WebCubit extends Cubit<WebState> {
).writeAsBytes(data!); ).writeAsBytes(data!);
return { 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(), "size": file.lengthSync(),
"width": asset.width, "width": asset.width,
"height": asset.height, "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, "fileType": file.path.split('/').last.split('.').last,
}; };
} }
...@@ -475,7 +624,7 @@ class WebCubit extends Cubit<WebState> { ...@@ -475,7 +624,7 @@ class WebCubit extends Cubit<WebState> {
// 请求麦克风权限 // 请求麦克风权限
var status = await Permission.microphone.request(); var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) { if (status != PermissionStatus.granted) {
throw RecordingPermissionException('麦克风权限未授权!'); throw RecordingPermissionException('no auth');
} }
if (state.recordState != 0) { if (state.recordState != 0) {
......
...@@ -5,10 +5,12 @@ import 'package:equatable/equatable.dart'; ...@@ -5,10 +5,12 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WechatAuthState extends Equatable { class WechatAuthState extends Equatable {
final String ip; final String ip;
final String? result; final String? result;
final String? sessionCode; final String? sessionCode;
final String? userCode; final String? userCode;
final String? classCode; final String? classCode;
...@@ -61,8 +63,8 @@ class WechatAuthCubit extends Cubit<WechatAuthState> { ...@@ -61,8 +63,8 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
_fluwx.addSubscriber(_responseListener); _fluwx.addSubscriber(_responseListener);
// _textEditingController = TextEditingController()..text = '127.0.0.1'; // _textEditingController = TextEditingController()..text = '127.0.0.1';
_textEditingController = TextEditingController()..text = 'appdev-th.banxiaoer.net'; // _textEditingController = TextEditingController()..text = 'appdev-th.banxiaoer.net';
// _textEditingController = TextEditingController()..text = 'appdev-xj.banxiaoer.net'; _textEditingController = TextEditingController()..text = 'appdev-xj.banxiaoer.net';
// _textEditingController = TextEditingController()..text = '192.168.1.136'; // _textEditingController = TextEditingController()..text = '192.168.1.136';
_wechatAuthRepository = getIt<WechatAuthRepository>(); _wechatAuthRepository = getIt<WechatAuthRepository>();
...@@ -75,15 +77,31 @@ class WechatAuthCubit extends Cubit<WechatAuthState> { ...@@ -75,15 +77,31 @@ class WechatAuthCubit extends Cubit<WechatAuthState> {
var data = resultData['data']; var data = resultData['data'];
var role = data['roles'][0]; 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( router.go(
'/web', '/web',
extra: { extra: {
'ip': state.ip, 'ip': state.ip,
'sessionCode': data['sessionCode'], 'sessionCode': sessionCode,
'userCode': data['userCode'], 'userCode': userCode,
'classCode': role['classCode'], 'classCode': classCode,
'userType': role['userType'], 'userType': userType,
'stuId': role['stuId'], 'stuId': stuId,
}, },
); );
} }
......
...@@ -8,6 +8,7 @@ import 'package:appframe/data/repositories/message/clipboard_data_handler.dart'; ...@@ -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/compress_handler.dart';
import 'package:appframe/data/repositories/message/device_info_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/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/image_info_handler.dart';
import 'package:appframe/data/repositories/message/location_handler.dart'; import 'package:appframe/data/repositories/message/location_handler.dart';
import 'package:appframe/data/repositories/message/network_type_handler.dart'; import 'package:appframe/data/repositories/message/network_type_handler.dart';
...@@ -154,6 +155,9 @@ Future<void> setupLocator() async { ...@@ -154,6 +155,9 @@ Future<void> setupLocator() async {
/// 设置标题和返回按钮 /// 设置标题和返回按钮
getIt.registerLazySingleton<MessageHandler>(() => SetTitleHandler(), instanceName: 'setTitle'); getIt.registerLazySingleton<MessageHandler>(() => SetTitleHandler(), instanceName: 'setTitle');
/// 登录
getIt.registerLazySingleton<MessageHandler>(() => GoLoginHandler(), instanceName: 'goLogin');
/// service /// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net')); 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/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 { 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 @override
Future<dynamic> handleMessage(params) async { Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) { if (params is! Map<String, dynamic>) {
...@@ -17,14 +28,6 @@ class ChooseVideoHandler extends MessageHandler { ...@@ -17,14 +28,6 @@ class ChooseVideoHandler extends MessageHandler {
if (sourceType != 'album' && sourceType != 'camera') { if (sourceType != 'album' && sourceType != 'camera') {
throw Exception('参数错误'); 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; int count = 1;
if (params.containsKey('count')) { if (params.containsKey('count')) {
...@@ -34,104 +37,143 @@ class ChooseVideoHandler extends MessageHandler { ...@@ -34,104 +37,143 @@ class ChooseVideoHandler extends MessageHandler {
} }
} }
int number = 60; int maxDuration = 60;
if (params.containsKey('number')) { if (params.containsKey('maxDuration')) {
number = params['number'] as int; maxDuration = params['maxDuration'] as int;
if (number < 1 || number > 60) { if (maxDuration < 1 || maxDuration > 600) {
throw Exception('参数错误'); throw Exception('参数错误');
} }
} }
// 从相册选择 _webCubit!.setChooseVideoCmdFlag(true, _message!);
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,
};
} }
} }
// 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 { ...@@ -23,12 +23,12 @@ class LocationHandler extends MessageHandler {
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission(); permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
throw '定位权限被拒绝'; throw 'no auth';
} }
} }
if (permission == LocationPermission.deniedForever) { if (permission == LocationPermission.deniedForever) {
throw '定位权限被永久拒绝'; throw 'no auth';
} }
final pos = await geolocator.getCurrentPosition(); final pos = await geolocator.getCurrentPosition();
......
...@@ -13,7 +13,7 @@ Future<bool> initLocalRecorder() async { ...@@ -13,7 +13,7 @@ Future<bool> initLocalRecorder() async {
// 请求麦克风权限 // 请求麦克风权限
var status = await Permission.microphone.request(); var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) { if (status != PermissionStatus.granted) {
throw RecordingPermissionException('麦克风权限未授权!'); throw RecordingPermissionException('no auth');
} }
if (!(!isLocalRecording && !isLocalPauseRecording && localRecordedFilePath == null)) { 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 { ...@@ -52,6 +52,8 @@ class MessageDispatcher {
h5Message.cmd == "getOrientation" || h5Message.cmd == "getOrientation" ||
h5Message.cmd == "getWindowInfo" || h5Message.cmd == "getWindowInfo" ||
h5Message.cmd == "chooseImage" || h5Message.cmd == "chooseImage" ||
h5Message.cmd == "chooseVideo" ||
h5Message.cmd == "goLogin" ||
h5Message.cmd.startsWith("audio") || h5Message.cmd.startsWith("audio") ||
h5Message.cmd.startsWith("setTitle")) { h5Message.cmd.startsWith("setTitle")) {
handler.setCubit(webCubit!); handler.setCubit(webCubit!);
......
import 'dart:io'; import 'dart:io';
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
class LocalServerService { class LocalServerService {
String? _httpDirectory;
// 启动本地HTTP服务器 // 启动本地HTTP服务器
Future<HttpServer> startLocalServer() async { Future<HttpServer> startLocalServer() async {
// 测试情况下, 每次启动服务,先解压dist文件
_extractDist();
HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 35982); HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 35982);
// HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8080);
print('本地服务器启动在端口: ${server.port}'); print('本地服务器启动在端口: ${server.port}');
server.listen((HttpRequest request) async { server.listen((HttpRequest request) async {
...@@ -15,14 +21,14 @@ class LocalServerService { ...@@ -15,14 +21,14 @@ class LocalServerService {
try { try {
if (requestPath.startsWith('/temp/')) { if (requestPath.startsWith('/temp/')) {
// 临时目录文件的请求 // 目录文件服务逻辑
await _serveTempFile(request, requestPath); await _serveTempFile(request, requestPath);
} else if (requestPath.startsWith('/test/')) { } else if (requestPath.startsWith('/test/')) {
// assets文件服务逻辑 // 内部assets文件服务逻辑
await _serveAssetFile(request, requestPath); await _serveAssetFile(request, requestPath);
} else { } else {
// asset/dist.zip 文件服务逻辑 // 内部集成H5文件服务逻辑
await _serveZipFileContent(request, requestPath); await _serveHttpFile(request, requestPath);
} }
} catch (e) { } catch (e) {
print('处理请求时出错: $e'); print('处理请求时出错: $e');
...@@ -36,10 +42,10 @@ class LocalServerService { ...@@ -36,10 +42,10 @@ class LocalServerService {
return server; return server;
} }
// 临时目录的文件 // 目录下的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async { Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try { try {
// 临时文件已经设备路径 // 临时文件路径
// 构建文件路径(移除 /temp 前缀) // 构建文件路径(移除 /temp 前缀)
final String filePath = requestPath.substring('/temp/'.length); final String filePath = requestPath.substring('/temp/'.length);
...@@ -68,52 +74,32 @@ class LocalServerService { ...@@ -68,52 +74,32 @@ class LocalServerService {
} }
} }
Future<void> _serveZipFileContent(HttpRequest request, String requestPath) async { Future<void> _serveHttpFile(HttpRequest request, String requestPath) async {
String zipAssetPath = 'assets/dist.zip';
try { try {
// 使用 rootBundle.load 加载资源文件 var httpDirectory = await getHttpDirectory();
final ByteData data = await rootBundle.load(zipAssetPath); final String filePath = '$httpDirectory$requestPath';
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('/')) { final File file = File(filePath);
requestPath = requestPath.substring(1); if (await file.exists()) {
} // 读取文件内容
final List<int> bytes = await file.readAsBytes();
if (zipFileName == requestPath) {
targetFile = file;
break;
}
}
if (targetFile == null) {
request.response request.response
..statusCode = HttpStatus.notFound ..headers.contentType = ContentType.parse(_getContentType(filePath))
..write('File not found in zip: $requestPath') ..add(bytes)
..close(); ..close();
return; } else {
}
// 返回文件内容
request.response request.response
..headers.contentType = ContentType.parse(_getContentType(requestPath)) ..statusCode = HttpStatus.notFound
..add(targetFile.content as List<int>) ..write('File not found: $filePath')
..close(); ..close();
}
} catch (e) { } catch (e) {
print('读取zip文件时出错: $e'); print('读取临时文件时出错: $e');
request.response request.response
..statusCode = HttpStatus.internalServerError ..statusCode = HttpStatus.notFound
..write('Error reading zip file') ..write('File not found')
..close(); ..close();
} }
} }
...@@ -148,4 +134,68 @@ class LocalServerService { ...@@ -148,4 +134,68 @@ class LocalServerService {
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg'; if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
return 'application/octet-stream'; 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!