Commit 3ce6d8ec by tanghuan

dev

1 parent 73f17c33
Showing 56 changed files with 2889 additions and 341 deletions
......@@ -5,10 +5,17 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application
android:label="班小二"
android:name="${applicationName}"
......
......@@ -4,5 +4,10 @@
<domain-config cleartextTrafficPermitted="true">
<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.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-config>
</network-security-config>
......@@ -20,23 +20,41 @@
<button onclick="setStorageSync()">设置本地缓存</button>
<button onclick="getStorageSync()">获取本地缓存</button>
<button onclick="removeStorageSync()">删除本地缓存</button>
<button onclick="clearStorageSync()">清空本地缓存</button>
<br>
<br>
<button onclick="chooseImage()">选择单个图片</button>
<button onclick="chooseImage(1)">选择单个图片</button>
<button onclick="chooseMultipleImage()">选择多个图片</button>
<br>
<br>
<button onclick="scanCode()">扫码测试</button>
<button onclick="camera()">录像测试</button>
<br>
<br>
<button onclick="openWeapp()">打开小程序</button>
<br>
<br>
<a href="/test/test2.html">跳转测试2</a>
<br>
<br>
<a href="/test/test3.html">跳转测试3</a>
<br>
<br>
<a href="/test/test4.html">跳转测试4</a>
<br>
<br>
<a href="/index.html">iOS跳转</a>
<br>
<br>
<script src="/test/test.js"></script>
......
......@@ -29,6 +29,11 @@ function getStorageSync() {
xeJsBridge.postMessage(message);
}
function removeStorageSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "removeStorageSync", "params": "test1" }';
xeJsBridge.postMessage(message);
}
function clearStorageSync() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "clearStorageSync", "params": {} }';
xeJsBridge.postMessage(message);
......@@ -60,3 +65,22 @@ function chooseMultipleImage() {
}
function scanCode() {
let params = {
"timestamp": 1, "unique": "123", "cmd": "scanCode", "params": {}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function openWeapp(){
let params = {
"timestamp": 1, "unique": "123", "cmd": "openWeapp", "params": {
appid:'gh_9a8d84445828',
path:'/pages/index/index?classCode=needswitch',
envVersion:'trial'
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试3</h2>
<div id="resp"></div>
<button onclick="selectFile()">选择文件</button>
<button onclick="saveImg()">保存图片视频</button>
<br>
<br>
<button onclick="setClipboard()">设置剪贴板</button>
<button onclick="getClipboard()">获取剪贴板</button>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
}
function selectFile() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'chooseFile',
params: {
count: 2,
fileTypes: ['docx', 'xlsx', 'pdf', 'mp4','csv']
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function saveImg() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'saveToAlbum',
params: {
// filePath: 'http://www.people.com.cn/NMediaFile/2025/0910/MAIN175747975880516PN6WLU10.jpg'
// filePath: '/data/user/0/cn.banxe.bxe/cache/1757576655307_1000019720.jpg'
filePath: '/data/user/0/cn.banxe.bxe/cache/1757577249083_VID_20250906_111114.mp4'
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function setClipboard() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "setClipboardData", "params": "此为测试剪贴板的内容" }';
xeJsBridge.postMessage(message);
}
function getClipboard() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "getClipboardData", "params": {} }';
xeJsBridge.postMessage(message);
}
</script>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>接口测试4</h2>
<div id="resp"></div>
<button onclick="clearResp()">清除响应数据</button>
<br>
<br>
<button onclick="compressImage()">测试图片压缩</button>
<br>
<br>
<button onclick="upload()">测试文件上传</button>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
// 清空响应数据
function clearResp() {
document.getElementById('resp').innerHTML = '';
}
function xeJsBridgeCallback(message) {
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
}
function compressImage() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'compressImage',
params: {
url: '/data/user/0/cn.banxe.bxe/cache/1757649757566_1000019639.jpg',
quality: 50,
compressedWidth: 800,
compressedHeight: 600,
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
function upload() {
let params = {
timestamp: 1,
unique: '123',
cmd: 'uploadFile',
params: {
tempFilePath: '/data/user/0/cn.banxe.bxe/cache/1758248263516_test.csv',
}
};
xeJsBridge.postMessage(JSON.stringify(params));
}
</script>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebView Communication</title>
</head>
<body>
<h2>前端测试</h2>
<form>
<input type="text" id="cliIp" value="">
<input type="submit">
</form>
<br>
<br>
<a href="/test/test.html">跳转测试1</a>
</body>
<script>
window.aaa = function (a, b, c) {
console.log(a, b, c);
}
function test(term, ) {
}
</script>
</html>
\ No newline at end of file
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
class RecorderState {
final bool recorderIsInit;
final String path;
RecorderState({this.recorderIsInit = false, this.path = ''});
}
class RecorderCubit extends Cubit<String> {
late FlutterSoundRecorder _recorder;
RecorderCubit(super.initialState, this._recorder) {
_recorder.openRecorder();
}
@override
Future<void> close() {
try {
_recorder.closeRecorder();
} catch (e) {
print(e);
}
return super.close();
}
}
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/data/repositories/message/player_handler.dart';
import 'package:appframe/data/repositories/message/recorder_handler.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:fluwx/fluwx.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebState extends Equatable {
final bool loaded;
final String title;
final bool needAuth;
final String? ip;
final String? sessionCode;
final String? userCode;
final String? classCode;
final int? userType;
final String? stuId;
const WebState(
this.loaded,
this.title,
this.needAuth,
final bool recorderIsInit;
final int recordState;
final String recordPath;
final bool playerIsInit;
final int playState;
final String playId;
const WebState({
this.loaded = false,
this.title = '界面加载中...',
this.ip,
this.sessionCode,
this.userCode,
this.classCode,
this.userType,
this.stuId,
);
this.recorderIsInit = false,
this.recordState = 0,
this.recordPath = '',
this.playerIsInit = false,
this.playState = 0,
this.playId = '',
});
WebState copyWith({
bool? loaded,
String? title,
bool? needAuth,
String? ip,
String? sessionCode,
String? userCode,
String? classCode,
int? userType,
String? stuId,
bool? recorderIsInit,
int? recordState,
String? recordPath,
bool? playerIsInit,
int? playState,
String? playId,
}) {
return WebState(
loaded ?? this.loaded,
title ?? this.title,
needAuth ?? this.needAuth,
sessionCode ?? this.sessionCode,
userCode ?? this.userCode,
classCode ?? this.classCode,
userType ?? this.userType,
stuId ?? this.stuId,
loaded: loaded ?? this.loaded,
title: title ?? this.title,
ip: ip ?? this.ip,
sessionCode: sessionCode ?? this.sessionCode,
userCode: userCode ?? this.userCode,
classCode: classCode ?? this.classCode,
userType: userType ?? this.userType,
stuId: stuId ?? this.stuId,
recorderIsInit: recorderIsInit ?? this.recorderIsInit,
recordState: recordState ?? this.recordState,
recordPath: recordPath ?? this.recordPath,
playerIsInit: playerIsInit ?? this.playerIsInit,
playState: playState ?? this.playState,
playId: playId ?? this.playId,
);
}
@override
List<Object?> get props => [loaded, title, needAuth];
List<Object?> get props => [
loaded,
title,
ip,
sessionCode,
userCode,
classCode,
userType,
stuId,
recorderIsInit,
recordState,
recordPath,
playerIsInit,
playState,
playId,
];
}
class WebCubit extends Cubit<WebState> {
late final MessageDispatcher _dispatcher;
late final WebViewController _controller;
late final HttpServer _server;
late final Fluwx _fluwx;
FlutterSoundRecorder? _recorder;
FlutterSoundPlayer? _player;
WebViewController get controller => _controller;
FlutterSoundRecorder? get recorder => _recorder;
FlutterSoundPlayer? get player => _player;
WebCubit(super.initialState) {
// 消息处理器
_dispatcher = MessageDispatcher();
......@@ -74,17 +129,19 @@ class WebCubit extends Cubit<WebState> {
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onUrlChange: (UrlChange url) {
print('onUrlChange===${url.url}');
},
onUrlChange: (UrlChange url) {},
onPageStarted: (String url) {
// 进行新页面加载时,关闭录音器和播放器,(如果有打开过)
// closeLocalRecorder() ;
// closeLocalRecorder();
// closeLocalPlayer();
},
onPageFinished: (String url) {
print("onPageFinished-------------------------$url");
controller.runJavaScript(
'document.querySelector("meta[name=viewport]").setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")',
);
finishLoading();
print('onPageFinished--------------------------------->');
print(url);
},
),
)
......@@ -92,6 +149,8 @@ class WebCubit extends Cubit<WebState> {
// 启动本地服务器,并加载HTML
_startLocalServerAndLoadHtml();
_fluwx = getIt.get<Fluwx>();
}
_startLocalServerAndLoadHtml() async {
......@@ -100,14 +159,15 @@ 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';
} else {
serverUrl =
'http://127.0.0.1:${_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}';
}
print("serverUrl===$serverUrl");
_controller.loadRequest(Uri.parse(serverUrl));
}
......@@ -118,7 +178,7 @@ class WebCubit extends Cubit<WebState> {
_dispatcher.dispatch(h5Message, (response) {
_sendResponse(response);
});
}, webCubit: this);
} catch (e) {
print('消息解析错误: $e');
}
......@@ -147,22 +207,258 @@ class WebCubit extends Cubit<WebState> {
}
//测试
void goAuth() {
emit(state.copyWith(needAuth: true));
void goWechatAuth() {
router.go('/wechatAuth');
}
//测试
void goAuth2() {
print("iOS 测试-------------------");
String serverUrl = 'http://127.0.0.1:${_server.port}/index.html';
void goAuth() {
String serverUrl = 'http://${state.ip}:${_server.port}/index.html';
// String serverUrl = 'http://localdev.banxiaoer.net';
_controller.loadRequest(Uri.parse(serverUrl));
}
void goMiniProgram() {
_fluwx
// ..addSubscriber(_responseListener)
..open(
target: MiniProgram(
username: 'gh_9a8d84445828',
path: '/pages/index/index?classCode=needswitch',
miniProgramType: WXMiniProgramType.preview,
),
);
}
// void _responseListener(response) {
// if (response is WeChatLaunchMiniProgramResponse) {
// print("小程序跳转 1 --------------------------------");
// print(response);
// }
// }
Future<String?> goScanCode() async {
var result = await router.push('/scanCode');
return result as String?;
}
Future<void> handleBack() async {
if (await _controller.canGoBack()) {
_controller.goBack();
}
}
/// 录音初始化
Future<bool> _initRecorder() async {
// 请求麦克风权限
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('麦克风权限未授权!');
}
if (state.recordState != 0) {
return false;
}
final directory = await getTemporaryDirectory();
String recordPath = '${directory.path}/${Uuid().v5(Namespace.url.value, 'www.banxiaoer.com')}_record.aac';
// 打开录音器
try {
final recorder = FlutterSoundRecorder();
_recorder = (await recorder.openRecorder())!;
emit(state.copyWith(recorderIsInit: true, recordPath: recordPath));
return true;
} catch (e) {
throw Exception('打开录音器失败!');
}
}
/// 开始录音
Future<bool> startRecording() async {
if (state.recorderIsInit) {
return false;
}
if (state.recordState != 0) {
return false;
}
final initResult = await _initRecorder();
if (!initResult) {
return false;
}
await _recorder!.startRecorder(toFile: state.recordPath, codec: Codec.aacMP4);
emit(state.copyWith(recordState: 1));
return true;
}
/// 暂停录音
Future<bool> pauseRecording() async {
if (!state.recorderIsInit) {
return false;
}
if (state.recordState != 1) {
return false;
}
await _recorder!.pauseRecorder();
emit(state.copyWith(recordState: 2));
return true;
}
/// 恢复录音
Future<bool> resumeRecording() async {
if (!state.recorderIsInit) {
return false;
}
if (state.recordState != 2) {
return false;
}
await _recorder!.resumeRecorder();
emit(state.copyWith(recordState: 1));
return true;
}
/// 停止录音
Future<Map<String, dynamic>> stopRecording() async {
if (!state.recorderIsInit) {
throw Exception("录音器未初始化");
}
if (state.recordState != 1) {
throw Exception("录音器状态错误");
}
var url = await _recorder!.stopRecorder();
await _recorder!.closeRecorder();
if (url == null || url.isEmpty) {
throw Exception("录音失败");
}
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
return {'url': url};
}
/// 清空录音
Future<bool> clearRecording() async {
// await _recorder!.stopRecorder();
await _recorder!.closeRecorder();
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
return true;
}
/// 播放初始化
Future<bool> _initPlayer() async {
// 打开播放器
try {
final player = FlutterSoundPlayer();
_player = (await player.openPlayer())!;
emit(state.copyWith(playerIsInit: true));
return true;
} catch (e) {
throw Exception('打开播放器失败!');
}
}
/// 播放音频
Future<bool> playAudio(String url) async {
if (!state.playerIsInit) {
final initResult = await _initPlayer();
if (!initResult) {
return false;
}
}
await _player!.startPlayer(
fromURI: url,
whenFinished: () async {
await _player!.stopPlayer();
emit(state.copyWith(playState: 0));
},
);
emit(state.copyWith(playState: 1));
return true;
}
/// 暂停播放
Future<bool> pauseAudio() async {
if (!state.playerIsInit) {
throw Exception("播放器未初始化");
}
if (state.playState != 1) {
throw Exception("播放器状态错误");
}
await _player!.pausePlayer();
emit(state.copyWith(playState: 2));
return true;
}
/// 恢复播放
Future<bool> resumeAudio() async {
if (!state.playerIsInit) {
throw Exception("播放器未初始化");
}
if (state.playState != 2) {
throw Exception("播放器状态错误");
}
await _player!.resumePlayer();
emit(state.copyWith(playState: 1));
return true;
}
/// 跳转播放
Future<bool> seekAudio(int seek) async {
if (!state.playerIsInit) {
throw Exception("播放器未初始化");
}
await _player!.seekToPlayer(Duration(seconds: seek));
emit(state.copyWith(playState: 1));
return true;
}
/// 停止播放
Future<bool> stopAudio() async {
if (!state.playerIsInit) {
throw Exception("播放器未初始化");
}
if (state.playState != 1) {
throw Exception("播放器状态错误");
}
await _player!.stopPlayer();
emit(state.copyWith(playState: 0));
return true;
}
/// 清空播放
Future<bool> clearAudio() async {
await _player?.closePlayer();
emit(state.copyWith(playerIsInit: false, playState: 0, playId: ''));
return true;
}
@override
Future<void> close() async {
_server.close();
closeLocalRecorder();
closeLocalPlayer();
// _fluwx.removeSubscriber(_responseListener);
// closeLocalRecorder();
// closeLocalPlayer();
await _recorder?.closeRecorder();
await _player?.closePlayer();
return super.close();
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
class WechatAuthState extends Equatable {
final String ip;
final String? result;
final String? sessionCode;
final String? userCode;
final String? classCode;
final int? userType;
final String? stuId;
final String? result;
const WechatAuthState(this.sessionCode, this.userCode, this.classCode, this.userType, this.stuId, this.result);
const WechatAuthState({
this.ip = '127.0.0.1',
this.result,
this.sessionCode,
this.userCode,
this.classCode,
this.userType,
this.stuId,
});
WechatAuthState copyWith({
String? ip,
String? result,
String? sessionCode,
String? userCode,
String? classCode,
int? userType,
String? stuId,
String? result,
}) {
return WechatAuthState(
sessionCode ?? this.sessionCode,
userCode ?? this.userCode,
classCode ?? this.classCode,
userType ?? this.userType,
stuId ?? this.stuId,
result ?? this.result,
ip: ip ?? this.ip,
result: result ?? this.result,
sessionCode: sessionCode ?? this.sessionCode,
userCode: userCode ?? this.userCode,
classCode: classCode ?? this.classCode,
userType: userType ?? this.userType,
stuId: stuId ?? this.stuId,
);
}
@override
List<Object?> get props => [sessionCode, userCode, classCode, userType, stuId, result];
List<Object?> get props => [ip, result, sessionCode, userCode, classCode, userType, stuId];
}
class WechatAuthCubit extends Cubit<WechatAuthState> {
late final Fluwx _fluwx;
late final WechatAuthRepository _wechatAuthRepository;
late final Fluwx _fluwx;
late final TextEditingController _textEditingController;
TextEditingController get textEditingController => _textEditingController;
WechatAuthCubit(super.initialState) {
_fluwx = Fluwx();
_register();
_subscribe();
_fluwx = getIt.get<Fluwx>();
_fluwx.addSubscriber(_responseListener);
// _textEditingController = TextEditingController()..text = '127.0.0.1';
_textEditingController = TextEditingController()..text = 'appdev-th.banxiaoer.net';
// _textEditingController = TextEditingController()..text = '192.168.1.136';
_wechatAuthRepository = getIt<WechatAuthRepository>();
}
Future<void> _register() async {
await _fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://univerallink.banxe.cn/link/");
}
void _responseListener(response) async {
if (response is WeChatAuthResponse) {
dynamic resultData = await _wechatAuthRepository.codeToSk(response.code!);
void _subscribe() {
_fluwx.addSubscriber((response) async {
if (response is WeChatAuthResponse) {
var result = 'state :${response.state} \n code:${response.code}';
// emit(state.copyWith(result: result));
dynamic data = await _wechatAuthRepository.codeToSk(response.code!);
print("===============================================");
print(data.toString());
var dd = data['data'];
var role = dd['roles'][0];
print(dd['sessionCode']);
print(dd['userCode']);
print(role['classCode']);
print(role['userType']);
print(role['stuId']);
emit(
state.copyWith(
sessionCode: dd['sessionCode'],
userCode: dd['userCode'],
classCode: role['classCode'],
userType: role['userType'],
stuId: role['stuId'],
),
);
}
});
var data = resultData['data'];
var role = data['roles'][0];
router.go(
'/web',
extra: {
'ip': state.ip,
'sessionCode': data['sessionCode'],
'userCode': data['userCode'],
'classCode': role['classCode'],
'userType': role['userType'],
'stuId': role['stuId'],
},
);
}
}
void auth() async {
emit(state.copyWith(ip: _textEditingController.text));
var result = await _fluwx.authBy(
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
);
print("++++++++++++++++++++++++++++++++++++++++++++++++++++++");
print(result);
if (!result) {
throw Exception('微信授权处理失败');
}
}
void goIndex() {
router.go('/web');
}
@override
Future<void> close() async {
_fluwx.removeSubscriber(_responseListener);
return super.close();
}
}
......@@ -7,7 +7,7 @@ late HttpServer localServer;
Future<void> startLocalServer() async {
HttpServer localServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
print('本地服务器启动在端口: ${localServer.port}');
// print('本地服务器启动在端口: ${localServer.port}');
localServer.listen((HttpRequest request) async {
final String requestPath = request.uri.path == '/' ? '/index.html' : request.uri.path;
......
import 'package:appframe/data/repositories/message/app_info_handler.dart';
import 'package:appframe/data/repositories/message/audio_player_handler.dart';
import 'package:appframe/data/repositories/message/audio_recorder_handler.dart';
import 'package:appframe/data/repositories/message/choose_file_handler.dart';
import 'package:appframe/data/repositories/message/choose_image_handler.dart';
import 'package:appframe/data/repositories/message/choose_video_handler.dart';
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/player_handler.dart';
import 'package:appframe/data/repositories/message/recorder_handler.dart';
import 'package:appframe/data/repositories/message/download_file_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';
import 'package:appframe/data/repositories/message/open_document_handler.dart';
import 'package:appframe/data/repositories/message/open_weapp_handler.dart';
import 'package:appframe/data/repositories/message/orientation_handler.dart';
import 'package:appframe/data/repositories/message/save_file_to_disk_handler.dart';
import 'package:appframe/data/repositories/message/save_to_album_handler.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/data/repositories/message/scan_code_handler.dart';
import 'package:appframe/data/repositories/message/storage_handler.dart';
import 'package:appframe/data/repositories/message/upload_file.dart';
import 'package:appframe/data/repositories/message/vibrate_short_handler.dart';
import 'package:appframe/data/repositories/message/video_info_handler.dart';
import 'package:appframe/data/repositories/message/wifi_info_handler.dart';
import 'package:appframe/data/repositories/message/window_info_handler.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:fluwx/fluwx.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
final getIt = GetIt.instance;
Future<void> setupLocator() async {
/// Fluwx
getIt.registerSingleton<Fluwx>(
await (() async {
Fluwx fluwx = Fluwx();
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://univerallink.banxe.cn/link/");
return fluwx;
})(),
);
///SharedPreferences
getIt.registerSingleton<SharedPreferences>(await SharedPreferences.getInstance());
/// 按指令名称注册 message handler
/// 视情况,对大概率会执行的指令直接加载,对概率较低的指令使用懒加载
/// 使用懒加载,用户实际用到了才会进行加载
/// 打开小程序
getIt.registerLazySingleton<MessageHandler>(() => OpenWeappHandler(), instanceName: 'openWeapp');
/// 设备信息
getIt.registerLazySingleton<MessageHandler>(() => DeviceInfoHandler(), instanceName: 'getDeviceInfoSync');
getIt.registerLazySingleton<MessageHandler>(() => DeviceInfoHandler(), instanceName: 'getDeviceInfo');
// 位置信息
getIt.registerLazySingleton<MessageHandler>(() => LocationHandler(), instanceName: 'getLocation');
// 网络信息
getIt.registerLazySingleton<MessageHandler>(() => NetworkTypeHandler(), instanceName: 'getNetworkType');
// wifi信息
getIt.registerLazySingleton<MessageHandler>(() => WifiInfoHandler(), instanceName: 'getWifiInfo');
/// 设备方向
getIt.registerLazySingleton<MessageHandler>(() => OrientationHandler(), instanceName: 'getOrientation');
/// 应用信息
getIt.registerLazySingleton<MessageHandler>(() => AppInfoHandler(), instanceName: 'getAppInfo');
/// 窗口信息
getIt.registerLazySingleton<MessageHandler>(() => WindowInfoHandler(), instanceName: 'getWindowInfo');
/// 本地缓存
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => GetStorageSyncHandler(), instanceName: 'getStorageSync');
getIt.registerLazySingleton<MessageHandler>(() => GetStorageHandler(), instanceName: 'getStorage');
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => SetStorageHandler(), instanceName: 'setStorage');
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => SetStorageSyncHandler(), instanceName: 'setStorageSync');
getIt.registerLazySingleton<MessageHandler>(() => RemoveStorageHandler(), instanceName: 'removeStorage');
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => ClearStorageSyncHandler(), instanceName: 'clearStorageSync');
getIt.registerLazySingleton<MessageHandler>(() => ClearStorageHandler(), instanceName: 'clearStorage');
/// 剪贴板
// Android已测试通过
......@@ -42,14 +94,35 @@ Future<void> setupLocator() async {
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => ChooseImageHandler(), instanceName: 'chooseImage');
/// 选择视频
getIt.registerLazySingleton<MessageHandler>(() => ChooseVideoHandler(), instanceName: 'chooseVideo');
/// 选择文件
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => ChooseFileHandler(), instanceName: 'chooseFile');
/// 图片信息
getIt.registerLazySingleton<MessageHandler>(() => ImageInfoHandler(), instanceName: 'getImageInfo');
/// 获取视频信息
getIt.registerLazySingleton<MessageHandler>(() => VideoInfoHandler(), instanceName: 'getVideoInfo');
/// 保存文件
getIt.registerLazySingleton<MessageHandler>(() => SaveFileToDisKHandler(), instanceName: 'saveFileToDisk');
/// 保存文件/视频到相册
// Android已测试通过
getIt.registerLazySingleton<MessageHandler>(() => SaveToAlbumHandler(), instanceName: 'saveToAlbum');
/// 图片压缩
getIt.registerLazySingleton<MessageHandler>(() => CompressImageHandler(), instanceName: 'compressImage');
/// 视频压缩
getIt.registerLazySingleton<MessageHandler>(() => CompressVideoHandler(), instanceName: 'compressVideo');
/// 保存文件到客户端文件系统
getIt.registerLazySingleton<MessageHandler>(() => OpenDocumentHandler(), instanceName: 'openDocument');
/// 录音
getIt.registerLazySingleton<MessageHandler>(() => AudioRecorderStartHandler(), instanceName: 'audioRecorderStart');
getIt.registerLazySingleton<MessageHandler>(() => AudioRecorderPauseHandler(), instanceName: 'audioRecorderPause');
......@@ -61,9 +134,22 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<MessageHandler>(() => AudioPlayHandler(), instanceName: 'audioPlay');
getIt.registerLazySingleton<MessageHandler>(() => AudioPauseHandler(), instanceName: 'audioPause');
getIt.registerLazySingleton<MessageHandler>(() => AudioResumeHandler(), instanceName: 'audioResume');
getIt.registerLazySingleton<MessageHandler>(() => AudioSeekHandler(), instanceName: 'audioSeek');
getIt.registerLazySingleton<MessageHandler>(() => AudioStopHandler(), instanceName: 'audioStop');
getIt.registerLazySingleton<MessageHandler>(() => AudioClearHandler(), instanceName: 'audioClear');
/// 震动
getIt.registerLazySingleton<MessageHandler>(() => VibrateShortHandler(), instanceName: 'vibrateShort');
/// 扫码
getIt.registerLazySingleton<MessageHandler>(() => ScanCodeHandler(), instanceName: 'scanCode');
/// 上传文件
getIt.registerLazySingleton<MessageHandler>(() => UploadFileHandler(), instanceName: 'uploadFile');
/// 下载文件
getIt.registerLazySingleton<MessageHandler>(() => DownloadFileHandler(), instanceName: 'downloadFile');
/// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net'));
......
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';
import 'package:flutter/material.dart';
......@@ -18,5 +19,11 @@ final GoRouter router = GoRouter(
return const WechatAuthPage();
},
),
GoRoute(
path: '/scanCode',
builder: (BuildContext context, GoRouterState state) {
return const ScanCodePage();
},
),
],
);
import 'package:fluwx/fluwx.dart';
final Fluwx fluwx = Fluwx();
Future<void> registerWechatApi() async {
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://univerallink.banxe.cn/link/");
}
import 'package:appframe/services/dispatcher.dart';
class AppInfoHandler extends MessageHandler {
@override
Future<Map<String, dynamic>> handleMessage(params) async {
return {"version": "0.1", "build": "light"};
}
}
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
class AudioPlayHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
bool result = await _webCubit!.playAudio(url);
return result;
}
}
class AudioPauseHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.pauseAudio();
return result;
}
}
class AudioResumeHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.resumeAudio();
return result;
}
}
class AudioSeekHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final seek = params['seek'] as int;
bool result = await _webCubit!.seekAudio(seek);
return result;
}
}
class AudioStopHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.stopAudio();
return result;
}
}
class AudioClearHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
await _webCubit!.clearAudio();
return true;
}
}
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
class AudioRecorderStartHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.startRecording();
return result;
}
}
class AudioRecorderPauseHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.pauseRecording();
return result;
}
}
class AudioRecorderResumeHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.resumeRecording();
return result;
}
}
class AudioRecorderStopHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
return await _webCubit!.stopRecording();
}
}
class AudioRecorderClearHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(dynamic params) async {
return await _webCubit!.clearRecording();
}
}
......@@ -16,8 +16,8 @@ class ChooseFileHandler extends MessageHandler {
throw Exception('参数错误');
}
var count = params['count'] as int;
var fileTypes = params['fileTypes'] as List<dynamic>;
final count = params['count'] as int;
final fileTypes = params['fileTypes'] as List<dynamic>;
FilePickerResult? filePickerResult = await FilePicker.platform.pickFiles(
type: FileType.custom,
......@@ -44,42 +44,43 @@ class ChooseFileHandler extends MessageHandler {
}
Future<Map<String, dynamic>> _handleFile(PlatformFile file) async {
// 获取临时目录
final tempDir = await getTemporaryDirectory();
// 临时文件路径
final uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}_${file.name}';
final tempFilePath = path.join(tempDir.path, uniqueFileName);
// final uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}_${file.name}';
// final tempFilePath = path.join(tempDir.path, uniqueFileName);
// 复制
// 所选中的文件已经被插件复制到临时目录,所以不需要再复制
final originalFile = File(file.path!);
final copiedFile = await originalFile.copy(tempFilePath);
// final copiedFile = await originalFile.copy(tempFilePath);
bool isImage = await FileTypeUtil.isImage(copiedFile);
bool isImage = await FileTypeUtil.isImage(originalFile);
bool isVideo = false;
if (!isImage) {
isVideo = await FileTypeUtil.isVideo(copiedFile);
isVideo = await FileTypeUtil.isVideo(originalFile);
}
// 通过image_size_getter获取图片尺寸
SizeResult? sizeResult;
String? thumbTempFilePath;
if (isImage) {
sizeResult = ImageSizeGetter.getSizeResult(FileInput(copiedFile));
thumbTempFilePath = await ThumbnailUtil.genTempThumbnail(copiedFile, tempDir);
sizeResult = ImageSizeGetter.getSizeResult(FileInput(originalFile));
thumbTempFilePath = await ThumbnailUtil.genTempThumbnail(originalFile, tempDir);
}
if (isVideo) {
thumbTempFilePath = await ThumbnailUtil.genVideoThumbnail(copiedFile.path, tempDir);
thumbTempFilePath = await ThumbnailUtil.genVideoThumbnail(originalFile.path, tempDir);
}
// 返回临时文件信息
return {
'tempFilePath': tempFilePath,
'tempFilePath': '/temp${originalFile.path}',
'size': file.size,
'width': sizeResult != null ? sizeResult.size.width : '',
'height': sizeResult != null ? sizeResult.size.height : '',
'thumbTempFilePath': thumbTempFilePath ?? '',
'fileType': copiedFile.path.split('/').last.split('.').last,
'fileType': originalFile.path.split('/').last.split('.').last,
};
}
}
......@@ -8,7 +8,7 @@ import 'package:image_size_getter/image_size_getter.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class ChooseImageHandler implements MessageHandler {
class ChooseImageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
......@@ -19,13 +19,20 @@ class ChooseImageHandler implements MessageHandler {
throw Exception('参数错误');
}
// 暂时忽略对此参数的处理
var sizeType = params['sizeType'] as List<dynamic>;
if (sizeType.isEmpty || sizeType.length > 2) {
throw Exception('参数错误');
List<dynamic>? sizeType;
if (params.containsKey('sizeType')) {
sizeType = params['sizeType'] as List<dynamic>;
if (sizeType.isEmpty || sizeType.length > 2) {
throw Exception('参数错误');
}
}
var count = params['count'] as int;
if (count < 1 || count > 9) {
throw Exception('参数错误');
int count = 9;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
throw Exception('参数错误');
}
}
// 从相册选择
......@@ -79,7 +86,7 @@ class ChooseImageHandler implements MessageHandler {
}
// 限制最多limit张
if(pickedFileList.length > limit) {
if (pickedFileList.length > limit) {
pickedFileList.removeRange(limit, pickedFileList.length);
}
......@@ -115,7 +122,7 @@ class ChooseImageHandler implements MessageHandler {
return [await _handleOne(pickedFile, tempDir)];
}
Future<Map<String, dynamic>> _handleOne(XFile pickedFile, Directory tempDir) async {
Future<Map<String, dynamic>> _handleOne3(XFile pickedFile, Directory tempDir) async {
// 生成唯一文件名
final String fileName = path.basename(pickedFile.path);
final String uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}_$fileName';
......@@ -137,11 +144,29 @@ class ChooseImageHandler implements MessageHandler {
"size": copiedFile.lengthSync(),
"width": sizeResult.size.width,
"height": sizeResult.size.height,
"thumbTempFilePath": thumbnailPath,
"thumbTempFilePath": '/temp$thumbnailPath',
"fileType": copiedFile.path.split('/').last.split('.').last,
};
}
Future<Map<String, dynamic>> _handleOne(XFile pickedFile, Directory tempDir) async {
var sourceFile = File(pickedFile.path);
// 通过image_size_getter获取图片尺寸
final sizeResult = ImageSizeGetter.getSizeResult(FileInput(sourceFile));
final thumbnailPath = await _genThumbnail(sourceFile, tempDir);
// 返回一个元素的数组
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,
};
}
Future<String?> _genThumbnail(File imageFile, Directory tempDir) async {
try {
// 缩略图路径
......
import 'dart:io';
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 {
@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 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:dio/dio.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:video_compress/video_compress.dart';
class CompressImageHandler implements MessageHandler {
/// 压缩图片
///
class CompressImageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
String url = params['url'];
int quality = params['quality'];
int compressedWidth = params['compressedWidth'];
int compressedHeight = params['compressedHeight'];
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String?;
if (url == null || url.isEmpty) {
throw Exception('参数错误');
}
final quality = params['quality'] as int?;
if (quality == null) {
throw Exception('参数错误');
}
var compressedWidth = params['compressedWidth'] as int?;
// if (compressedWidth == null) {
// throw Exception('参数错误');
// }
var compressedHeight = params['compressedHeight'] as int?;
// if (compressedHeight == null) {
// throw Exception('参数错误');
// }
if (compressedWidth == null && compressedHeight == null) {
throw Exception('参数错误');
}
if (compressedWidth == null) {
compressedWidth = compressedHeight;
} else if (compressedHeight == null) {
compressedHeight = compressedWidth;
}
var originFile = File(url);
// 获取后缀名
String ext = path.extension(url);
final Directory tempDir = await getTemporaryDirectory();
String srcPath;
// 如果是远程文件,先进行下载
if (url.startsWith('http')) {
srcPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
// Dio 下载文件
final resp = await Dio().download(url, srcPath);
if (resp.statusCode != 200) {
throw Exception('远程文件下载失败');
}
} else {
srcPath = url;
}
var originFile = File(srcPath);
if (!originFile.existsSync()) {
throw Exception('文件不存在');
}
final String uniqueFileName = '${DateTime.now().millisecondsSinceEpoch}$ext';
final String tempPath = path.join(tempDir.path, uniqueFileName);
var result = await FlutterImageCompress.compressAndGetFile(
// 压缩处理
var croppedFile = await FlutterImageCompress.compressAndGetFile(
originFile.absolute.path,
uniqueFileName,
quality: 88,
rotate: 180,
tempPath,
quality: quality,
minWidth: compressedWidth!,
minHeight: compressedHeight!,
);
return true;
if (croppedFile == null) {
throw Exception('图片压缩失败');
}
return {"tempFilePath": "/temp${croppedFile.path}", "size": await croppedFile.length()};
}
}
class CompressVideoHandler implements MessageHandler {
/// 压缩视频
///
class CompressVideoHandler extends MessageHandler {
@override
Future handleMessage(dynamic params) {
// TODO: implement handleMessage
throw UnimplementedError();
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String?;
if (url == null || url.isEmpty) {
throw Exception('参数错误');
}
final quality = params['quality'] as String?;
if (quality == null || quality.isEmpty) {
throw Exception('参数错误');
}
final resolution = params['resolution'] as double?;
if (resolution == null || resolution > 1 || resolution <= 0) {
throw Exception('参数错误');
}
String srcPath;
if (url.startsWith('http')) {
String ext = path.extension(url);
final Directory tempDir = await getTemporaryDirectory();
srcPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
// Dio 下载文件
final resp = await Dio().download(url, srcPath);
if (resp.statusCode != 200) {
throw Exception('远程文件下载失败');
}
} else {
srcPath = url;
}
VideoQuality videoQuality;
switch (quality) {
case 'low':
videoQuality = VideoQuality.LowQuality;
break;
case 'middle':
videoQuality = VideoQuality.MediumQuality;
break;
case 'high':
videoQuality = VideoQuality.HighestQuality;
break;
default:
throw Exception('参数错误');
}
final mediaInfo = await VideoCompress.compressVideo(
srcPath,
quality: videoQuality,
deleteOrigin: false,
includeAudio: true,
);
return {"tempFilePath": "/temp${mediaInfo!.path}", "size": mediaInfo.filesize};
}
}
import 'package:appframe/services/dispatcher.dart';
class CropImageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
var url = params['sourceType'] as String?;
if (url == null || url.isEmpty) {
throw Exception('参数错误');
}
var cropScale = params['scale'] as String?;
if (cropScale == null || cropScale.isEmpty) {
throw Exception('参数错误');
}
var scaleArray = ['16:9', '9:16', '4:3', '3:4', '5:4', '4:5', '1:1'];
if (!scaleArray.contains(cropScale)) {
throw Exception('参数错误');
}
return false;
}
}
......@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:device_info_plus/device_info_plus.dart';
class DeviceInfoHandler implements MessageHandler {
class DeviceInfoHandler extends MessageHandler {
@override
Future<Map<String, dynamic>> handleMessage(dynamic params) async {
var deviceInfoPlugin = DeviceInfoPlugin();
......@@ -12,11 +12,15 @@ class DeviceInfoHandler implements MessageHandler {
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
return {
"brand": androidInfo.brand,
"model": androidInfo.model,
"system": androidInfo.version.release,
"platform": "Android",
"memorySize": (androidInfo.physicalRamSize ~/ (1024 * 1024)).toString(),
'abi': '${androidInfo.supportedAbis}',
'deviceAbi': androidInfo.supportedAbis.isNotEmpty ? androidInfo.supportedAbis[0] : '',
'benchmarkLevel': -1,
'brand': androidInfo.brand,
'model': androidInfo.model,
'system': androidInfo.version.release,
'platform': "Android",
'cpuType': androidInfo.hardware,
"memorySize": androidInfo.physicalRamSize,
};
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
......
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class DownloadFileHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
// 获取后缀名
String ext = path.extension(url);
// 获取应用文档目录路径
final Directory tempDir = await getApplicationDocumentsDirectory();
final targetPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
final resp = await Dio().download(url, targetPath);
if (resp.statusCode != 200) {
throw Exception('文件下载失败');
}
return {'tempFilePath': '/temp$targetPath'};
}
}
import 'dart:io';
import 'dart:ui' as ui;
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart';
import 'package:dio/dio.dart';
import 'package:exif/exif.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class ImageInfoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
String filePath;
if (url.startsWith('http')) {
// 获取后缀名
String ext = path.extension(url);
// 获取应用文档目录路径
final Directory tempDir = await getApplicationDocumentsDirectory();
final targetPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
final resp = await Dio().download(url, targetPath);
if (resp.statusCode != 200) {
throw Exception('文件下载失败');
}
filePath = targetPath;
} else {
filePath = url;
}
// 读取图片信息
final file = File(filePath);
if (!file.existsSync()) {
throw Exception('图片文件不存在');
}
final bytes = await file.readAsBytes();
// 获取基本图片信息(宽高)
final codec = await ui.instantiateImageCodec(bytes);
final frameInfo = await codec.getNextFrame();
final image = frameInfo.image;
int width = image.width;
int height = image.height;
// 读取 EXIF 信息
int? orientation;
final exifData = await readExifFromBytes(bytes);
if (exifData.isNotEmpty && exifData.containsKey('Image Orientation')) {
final orientationTag = exifData['Image Orientation'];
if (orientationTag != null) {
orientation = orientationTag.values.firstAsInt();
}
}
String orientationStr;
switch (orientation) {
case 1:
orientationStr = 'up';
break;
case 2:
orientationStr = 'up-mirrored';
break;
case 3:
orientationStr = 'down';
break;
case 4:
orientationStr = 'down-mirrored';
break;
case 5:
orientationStr = 'left-mirrored';
break;
case 6:
orientationStr = 'left';
break;
case 8:
orientationStr = 'right';
break;
default:
orientationStr = 'up';
}
// 获取MIME类型并转换为文件扩展名
final mimeType = await FileTypeUtil.getMimeType(file);
final fileExtension = FileTypeUtil.getExtensionFromMime(mimeType);
return {
'tempFilePath': '/temp$filePath',
'width': width,
'height': height,
'orientation': orientationStr,
'type': fileExtension,
};
}
}
import 'package:appframe/services/dispatcher.dart';
import 'package:geolocator/geolocator.dart';
class LocationHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
// if (params is! Map<String, dynamic>) {
// throw Exception('参数错误');
// }
//
// var type = params['type'] as String?;
// type = type ?? 'gcj02';
GeolocatorPlatform geolocator = GeolocatorPlatform.instance;
// 检查定位服务是否启用
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
throw '定位服务未启用';
}
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
throw '定位权限被拒绝';
}
}
if (permission == LocationPermission.deniedForever) {
throw '定位权限被永久拒绝';
}
final pos = await geolocator.getCurrentPosition();
return {
'latitude': pos.latitude,
'longitude': pos.longitude,
'speed': pos.speed,
'accuracy': pos.accuracy,
'altitude': pos.altitude,
'verticalAccuracy': 0,
'horizontalAccuracy': 0,
};
}
}
import 'package:appframe/services/dispatcher.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
class NetworkTypeHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
var connectivity = Connectivity();
final List<ConnectivityResult> connectivityResult = await connectivity.checkConnectivity();
String networkType;
if (connectivityResult.contains(ConnectivityResult.wifi)) {
// Wi-fi is available.
// Note for Android:
// When both mobile and Wi-Fi are turned on system will return Wi-Fi only as active network type
networkType = "wifi";
} else if (connectivityResult.contains(ConnectivityResult.mobile)) {
// Mobile network available.
networkType = "mobile";
} else if (connectivityResult.contains(ConnectivityResult.ethernet)) {
// Ethernet connection available.
networkType = "ethernet";
} /*else if (connectivityResult.contains(ConnectivityResult.vpn)) {
// Vpn connection active.
// Note for iOS and macOS:
// There is no separate network interface type for [vpn].
// It returns [other] on any device (also simulator)
} else if (connectivityResult.contains(ConnectivityResult.bluetooth)) {
// Bluetooth connection available.
} */else if (connectivityResult.contains(ConnectivityResult.other)) {
// Connected to a network which is not in the above mentioned networks.
networkType = "none";
} else if (connectivityResult.contains(ConnectivityResult.none)) {
// No available network types
networkType = "none";
} else {
networkType = "none";
}
return {
"networkType": networkType,
"signalStrength":1,
"weakNet":false
};
}
}
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
class OpenDocumentHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String?;
if (url == null || url.isEmpty) {
throw Exception('参数错误');
}
if (url.startsWith("http://")) {
await saveNetworkImageToExternalStorage(url);
} else {
await saveLocalFileToExternalStorage(url);
}
}
Future<bool> saveLocalFileToExternalStorage(String sourcePath) async {
try {
// 获取外部存储目录
final directory = await getExternalStorageDirectory();
// 获取文件名
final fileName = sourcePath.split('/').last;
var destinationPath = '${directory?.path}/$fileName';
// 检查文件是否存在,如果存在则生成新文件名
var counter = 1;
while (await File(destinationPath).exists()) {
final extension = fileName.split('.').last;
final nameWithoutExtension = fileName.substring(0, fileName.length - extension.length - 1);
destinationPath = '${directory?.path}/${nameWithoutExtension}_$counter.$extension}';
counter++;
}
// 复制文件
final sourceFile = File(sourcePath);
await sourceFile.copy(destinationPath);
return true;
} catch (e) {
print('保存文件失败: $e');
return false;
}
}
Future<bool> saveNetworkImageToExternalStorage(String imageUrl) async {
try {
// 获取外部存储目录
final directory = await getExternalStorageDirectory();
// 从URL获取文件名
final fileName = imageUrl.split('/').last;
var filePath = '${directory?.path}/$fileName';
// 检查文件是否存在,如果存在则生成新文件名
var counter = 1;
while (await File(filePath).exists()) {
final extension = fileName.split('.').last;
final nameWithoutExtension = fileName.substring(0, fileName.length - extension.length - 1);
filePath = '${directory?.path}/${nameWithoutExtension}_$counter.$extension}';
counter++;
}
// 下载网络图片
final response = await http.get(Uri.parse(imageUrl));
if (response.statusCode == 200) {
// 保存到外部存储
final file = File(filePath);
await file.writeAsBytes(response.bodyBytes);
return true;
} else {
print('下载失败: ${response.statusCode}');
return false;
}
} catch (e) {
print('保存失败: $e');
return false;
}
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:fluwx/fluwx.dart';
class OpenWeappHandler extends MessageHandler {
late Fluwx _fluwx;
@override
Future<bool> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final appid = params['appid'] as String;
final path = params['path'] as String;
final envVersion = params['envVersion'] as String?;
if (appid.isEmpty || path.isEmpty) {
throw Exception('参数错误');
}
if (envVersion != null && envVersion != 'release' && envVersion != 'trial' && envVersion != 'develop') {
throw Exception('参数错误');
}
_fluwx = getIt.get<Fluwx>();
// _fluwx.addSubscriber(_responseListener);
try {
return await _fluwx.open(
target: MiniProgram(username: appid, path: path, miniProgramType: _getWXMiniProgramType(envVersion)),
);
} catch (e) {
print(e);
return false;
}
}
// void _responseListener(response) {
// print("response--------------------------------");
// if (response is WeChatLaunchMiniProgramResponse) {
// print("小程序跳转 2 --------------------------------");
// print(response);
// _fluwx.removeSubscriber(_responseListener);
// }
// }
WXMiniProgramType _getWXMiniProgramType(String? envVersion) {
switch (envVersion) {
case 'release':
return WXMiniProgramType.release;
case 'trial':
return WXMiniProgramType.preview;
case 'develop':
return WXMiniProgramType.test;
default:
return WXMiniProgramType.preview;
}
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
class OrientationHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
final orientation = getIt.get<SharedPreferences>().get("orientation");
return {'type': orientation ?? 'portrait'};
}
}
......@@ -97,42 +97,42 @@ Future<void> _resetPlayerStatus() async {
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;
}
}
// 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;
// }
// }
......@@ -133,41 +133,41 @@ Future<void> _resetRecorderStatus() async {
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;
}
}
// 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:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
class SaveFileToDisKHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
var filePath = params['filePath'] as String;
if (filePath.isEmpty) {
throw Exception('参数错误');
}
String ext = path.extension(filePath);
final Directory tempDir = await getApplicationDocumentsDirectory();
final targetPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
if (filePath.startsWith('http')) {
final resp = await Dio().download(filePath, targetPath);
if (resp.statusCode != 200) {
throw Exception('文件下载失败');
}
return true;
} else {
// 将filePath路径的文件保存到targetPath
final f = await File(filePath).copy(targetPath);
if (f.existsSync()) {
return true;
} else {
return false;
}
}
}
}
import 'package:appframe/services/dispatcher.dart';
import 'package:gallery_saver_plus/gallery_saver.dart';
class SaveToAlbumHandler implements MessageHandler {
class SaveToAlbumHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
......
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';
class ScanCodeHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
Future<dynamic> handleMessage(params) async {
try {
final result = await _webCubit!.goScanCode();
// 返回扫码结果
return result;
} finally {
_unfollowCubit();
}
}
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@ import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SetStorageSyncHandler extends MessageHandler {
class SetStorageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
......@@ -24,7 +24,7 @@ class SetStorageSyncHandler extends MessageHandler {
}
}
class GetStorageSyncHandler extends MessageHandler {
class GetStorageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! String || params.isEmpty) {
......@@ -39,7 +39,22 @@ class GetStorageSyncHandler extends MessageHandler {
}
}
class ClearStorageSyncHandler extends MessageHandler {
class RemoveStorageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! String || params.isEmpty) {
throw Exception('参数错误');
}
try {
return await getIt.get<SharedPreferences>().remove(params);
} catch (e) {
throw Exception(e.toString());
}
}
}
class ClearStorageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
return await getIt.get<SharedPreferences>().clear();
......
import 'dart:io';
import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as path;
import 'package:uuid/uuid.dart';
class UploadFileHandler extends MessageHandler {
@override
Future handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final String? tempFilePath = params['tempFilePath'] as String?;
if (tempFilePath == null || tempFilePath.isEmpty) {
throw Exception('参数错误');
}
// final result = await compute(_handleUpload, {'filePath': tempFilePath});
final result = await _handleUpload({'filePath': tempFilePath});
return result;
}
static const _bxeBaseUrl = 'https://iotapp-dev.banxiaoer.com/iotapp';
static const _signatureNewUrl = '/api/v1/obs/multipart/signaturenew';
static const _signatureNextUrl = '/api/v1/obs/multipart/signaturenext';
static const _completeUrl = '/api/v1/obs/multipart/complete';
/// 在Isolate中执行
static Future<Map<String, dynamic>> _handleUpload(Map<String, dynamic> fileParams) async {
var filePath = fileParams['filePath'] as String;
if (filePath.startsWith('/temp')) {
filePath = filePath.substring(5);
}
final bxeApiService = ApiService(baseUrl: _bxeBaseUrl);
// 由于服务端签名时未设置Content-Type,这里必须设置为空,否则会报签名错误
// 由于封装有默认值,所以不能不设置
final obsApiService = ApiService(defaultHeaders: {'Content-Type': '', 'Accept': ''});
//并行上传分段
final uploadResult = await _uploadInParallel(bxeApiService, obsApiService, filePath);
String objectKey = uploadResult['objectKey'] as String;
String bucket = uploadResult['bucket'] as String;
String uploadId = uploadResult['uploadId'] as String;
Map<int, String> tagsMap = uploadResult['tagsMap'] as Map<int, String>;
//请求合并文件
Response response = await _merge(bxeApiService, objectKey, bucket, uploadId, tagsMap);
//关闭Dio
bxeApiService.close();
obsApiService.close();
return {'location': response.data['location']};
}
/// 并行上传
static Future<Map<String, dynamic>> _uploadInParallel(
ApiService bxeApiService,
ApiService obsApiService,
String filePath, {
int maxConcurrency = 5,
}) async {
//判断文件
File file = File(filePath);
if (!file.existsSync()) {
throw Exception('文件不存在');
}
//暂时仅支持200M的文件上传
final fileSize = file.lengthSync();
if (fileSize > 1024 * 1024 * 200) {
throw Exception('上传的文件过大');
}
//分段大小1M
final chunkSize = 1024 * 1024 * 1;
//分段总数
final totalChunks = (fileSize / chunkSize).ceil();
final randomAccessFile = file.openSync();
//bucket 存储桶名称 : bxe-files | bxe-pics | bxe-videos
String bucket;
if (await FileTypeUtil.isImage(file)) {
bucket = 'bxe-pics';
} else if (await FileTypeUtil.isVideo(file)) {
bucket = 'bxe-videos';
} else {
bucket = 'bxe-files';
}
//生成唯一文件名,
// String objectKey = 'd2/test/file.csv';
var uuid = Uuid();
String objectKey = '${uuid.v5(Namespace.url.value, 'www.banxiaoer.com')}${path.extension(file.path)}';
String uploadId = '';
Map<int, String> tagsMap = {};
// final futures = <Future>[];
for (int i = 0; i < totalChunks; i++) {
// 控制并发数量
// if (futures.length >= maxConcurrency) {
// await Future.wait(futures);
// futures.clear();
// }
final start = i * chunkSize;
final actualChunkSize = (i + 1) * chunkSize > fileSize ? fileSize - start : chunkSize;
final chunk = Uint8List(actualChunkSize);
randomAccessFile.setPositionSync(start);
await randomAccessFile.readInto(chunk, 0, actualChunkSize);
String chunkSignUrl;
if (i == 0) {
final initResult = await _init(bxeApiService, objectKey, bucket);
uploadId = initResult['upload_id'] as String;
chunkSignUrl = initResult['signed_url'] as String;
} else {
final nextResult = await _next(bxeApiService, objectKey, bucket, uploadId, i + 1);
chunkSignUrl = nextResult['signed_url'] as String;
}
await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
// final future = _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
// futures.add(future);
}
// 等待剩余的上传完成
// if (futures.isNotEmpty) {
// await Future.wait(futures);
// }
randomAccessFile.closeSync();
return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap};
}
/// 初始化,请求后端获取签名信息和上传任务ID
static Future<Map<String, dynamic>> _init(ApiService bxeApiService, String objectKey, String bucket) async {
var endpoint = '$_signatureNewUrl?objectKey=$objectKey&bucket=$bucket';
final resp = await bxeApiService.get(endpoint);
return resp.data;
}
/// 每次上传前,请求后端获取签名信息
static Future<Map<String, dynamic>> _next(
ApiService bxeApiService,
String objectKey,
String bucket,
String uploadId,
int partNum,
) async {
var endpoint = '$_signatureNextUrl?objectKey=$objectKey&bucket=$bucket&uploadId=$uploadId&partNum=$partNum';
final resp = await bxeApiService.get(endpoint);
return resp.data;
}
/// 上传段,按照最大重试次数进行上传重试
static Future<void> _uploadChunkWithRetry(
ApiService obsApiService,
String signUrl,
int chunkIndex,
Uint8List chunk,
Map<int, String> tagsMap, {
int maxRetries = 3,
}) async {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
final resp = await _uploadChunk(obsApiService, signUrl, chunk);
if (resp.statusCode == 200) {
final etags = resp.headers['etag'] as List<String>;
tagsMap[chunkIndex + 1] = etags[0];
return; // 上传成功
} else {
print('Chunk $chunkIndex upload failed: ${resp.statusCode} ${resp.data}');
throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}');
}
} catch (e) {
if (attempt == maxRetries) {
throw Exception('Chunk $chunkIndex upload failed after $maxRetries attempts: $e');
}
// 等待后重试
await Future.delayed(Duration(seconds: 2 * attempt));
}
}
}
/// 上传段
static Future<Response> _uploadChunk(ApiService obsApiService, String signUrl, Uint8List chunk) async {
var url = signUrl.replaceFirst('AWSAccessKeyId=', 'AccessKeyId=').replaceFirst(':443', '');
try {
Response response = await obsApiService.put(url, chunk);
return response;
} catch (e) {
print('Chunk upload failed: $e');
throw Exception('Chunk upload failed: $e');
}
}
/// 请求合并文件
static Future<Response> _merge(
ApiService bxeApiService,
String objectKey,
String bucket,
String uploadId,
Map<int, String> tagsMap,
) async {
final parts = [];
for (int i = 1; i <= tagsMap.length; i++) {
parts.add({'partNumber': i, 'etag': tagsMap[i]});
}
final response = await bxeApiService.post(_completeUrl, {
'objectKey': objectKey,
'bucket': bucket,
'uploadId': uploadId,
'parts': parts,
});
if (response.statusCode != 200) {
throw Exception('合并文件失败');
}
return response;
}
}
import 'package:appframe/services/dispatcher.dart';
import 'package:vibration/vibration.dart';
class VibrateShortHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params != null && params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
int? duration;
int? amplitude;
if (params != null) {
var type = params['type'] as String?;
type ??= 'light';
duration = params['duration'] as int?;
duration ??= 15;
if (type == 'light') {
amplitude = 10;
} else if (type == 'medium') {
amplitude = 125;
} else if (type == 'heavy') {
amplitude = 250;
} else {
amplitude = 10;
}
}
Vibration.vibrate(duration: duration ?? 15, amplitude: amplitude ?? 10);
return true;
}
}
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:video_compress/video_compress.dart';
class VideoInfoHandler extends MessageHandler {
@override
Future handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
String filePath;
if (url.startsWith('http')) {
// 获取后缀名
String ext = path.extension(url);
// 获取应用文档目录路径
final Directory tempDir = await getApplicationDocumentsDirectory();
final targetPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
final resp = await Dio().download(url, targetPath);
if (resp.statusCode != 200) {
throw Exception('文件下载失败');
}
filePath = targetPath;
} else {
filePath = url;
}
final file = File(filePath);
if (!file.existsSync()) {
throw Exception('视频文件不存在');
}
// 使用video_compress获取视频信息
final mediaInfo = await VideoCompress.getMediaInfo(filePath);
// 获取文件大小
final size = await file.length();
// 获取MIME类型并转换为文件扩展名
final mimeType = await FileTypeUtil.getMimeType(file);
final fileExtension = FileTypeUtil.getExtensionFromMime(mimeType);
return {
'tempFilePath': '/temp$filePath',
'width': mediaInfo.width ?? 0,
'height': mediaInfo.height ?? 0,
'type': fileExtension,
'duration': (mediaInfo.duration ?? 0) / 1000, // 转换为秒
'size': size,
};
}
}
import 'package:appframe/services/dispatcher.dart';
import 'package:network_info_plus/network_info_plus.dart';
class WifiInfoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
final info = NetworkInfo();
// final wifiName = await info.getWifiName(); // "FooNetwork"
// final wifiBSSID = await info.getWifiBSSID(); // 11:22:33:44:55:66
// final wifiIP = await info.getWifiIP(); // 192.168.1.43
// final wifiIPv6 = await info.getWifiIPv6(); // 2001:0db8:85a3:0000:0000:8a2e:0370:7334
// final wifiSubmask = await info.getWifiSubmask(); // 255.255.255.0
// final wifiBroadcast = await info.getWifiBroadcast(); // 192.168.1.255
// final wifiGateway = await info.getWifiGatewayIP(); // 192.168.1.1
// SSID: string, // Wi-Fi 的 SSID
// BSSID: string, // Wi-Fi 的 BSSID
// secure: boolean, // Wi-Fi 是否安全
// signalStrength: number, // Wi-Fi 信号强度, 安卓取值 0 ~ 100 ,iOS 取值 0 ~ 1 ,值越大强度越大
// frequency: number // Wi-Fi 频段单位 MHz
return {
// 'SSID': ''
'BSSID': await info.getWifiBSSID(),
// 'secure': null,
// 'signalStrength': 100,
// 'frequency':''
};
}
}
\ No newline at end of file
import 'dart:convert';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WindowInfoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
final windowInfo = getIt.get<SharedPreferences>().getString('windowInfo');
return jsonDecode(windowInfo!);
}
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/wechat.dart';
import 'package:flutter/material.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await registerWechatApi();
await setupLocator();
runApp(const App());
......
import 'package:dio/dio.dart';
// // 使用通用的 ApiService
// final apiService = ApiService(baseUrl: 'https://bxe.cn');
//
// // 发送POST请求
// final response = await apiService.post('/users', {
// 'name': 'xiaoming',
// 'email': 'xiaoming@example.com'
// });
class ApiService {
late Dio _dio;
......@@ -59,8 +50,28 @@ class ApiService {
);
}
/// 发送PUT请求
Future<Response<T>> put<T>(
String endpoint,
dynamic data, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
Options? options,
}) async {
return await _dio.put<T>(
endpoint,
data: data,
queryParameters: queryParameters,
options: options?.copyWith(headers: {...?headers}),
);
}
/// 添加拦截器
void addInterceptor(Interceptor interceptor) {
_dio.interceptors.add(interceptor);
}
void close() {
_dio.close();
}
}
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/data/models/message/h5_resp.dart';
......@@ -7,6 +8,8 @@ abstract class MessageHandler {
// Future<Map<String, dynamic>> handleMessage(Map<String, dynamic> params);
// Future<dynamic> handleMessage(Map<String, dynamic> params);
Future<dynamic> handleMessage(dynamic params);
void setCubit(WebCubit cubit) {}
}
// 消息分发器
......@@ -24,7 +27,7 @@ class MessageDispatcher {
}
// 分发处理
Future<void> dispatch(H5Message h5Message, Function callback) async {
Future<void> dispatch(H5Message h5Message, Function callback, {WebCubit? webCubit}) async {
var handler = _handlers[h5Message.cmd];
if (handler == null) {
try {
......@@ -39,6 +42,11 @@ class MessageDispatcher {
}
try {
// 设置传递的cubit,进行业务操作
if (h5Message.cmd == 'scanCode' || h5Message.cmd.startsWith("audio")) {
handler.setCubit(webCubit!);
}
final result = await handler.handleMessage(h5Message.params);
H5Resp h5Resp = H5Resp(h5Message.unique, h5Message.cmd, result, '');
callback(h5Resp.toJson());
......
......@@ -6,7 +6,8 @@ import 'package:flutter/services.dart';
class LocalServerService {
// 启动本地HTTP服务器
Future<HttpServer> startLocalServer() async {
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}');
server.listen((HttpRequest request) async {
......
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class ScanCodePage extends StatelessWidget {
const ScanCodePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('扫码'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
context.pop();
},
),
),
body: MobileScanner(
onDetect: (BarcodeCapture barcode) {
final String? code = barcode.barcodes.first.rawValue;
if (code != null) {
// 返回扫码结果
context.pop(code);
}
},
fit: BoxFit.contain,
),
);
}
}
\ No newline at end of file
import 'dart:convert';
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/config/locator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebPage extends StatelessWidget {
class WebPage extends StatefulWidget {
const WebPage({super.key});
@override
State<WebPage> createState() => _WebPageState();
}
class _WebPageState extends State<WebPage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Orientation? _lastOrientation;
/// 用于监听处理屏幕方向变化
@override
Future<void> didChangeMetrics() async {
print('didChangeMetrics------------------------------->');
final mediaQuery = MediaQuery.of(context);
// 方向
final orientation = mediaQuery.orientation;
if (orientation != _lastOrientation) {
_lastOrientation = orientation;
await getIt.get<SharedPreferences>().setString(
'orientation',
orientation == Orientation.portrait ? "portrait" : "landscape",
);
final viewPadding = mediaQuery.viewPadding;
final size = mediaQuery.size;
final safeArea = mediaQuery.padding;
final devicePixelRatio = mediaQuery.devicePixelRatio;
// 计算安全区域坐标
final safeAreaLeft = safeArea.left;
final safeAreaRight = size.width - safeArea.right;
final safeAreaTop = safeArea.top;
final safeAreaBottom = size.height - safeArea.bottom;
final safeAreaWidth = size.width - safeArea.horizontal;
final safeAreaHeight = size.height - safeArea.vertical;
final windowInfo = {
'pixelRatio': devicePixelRatio,
'screenWidth': size.width * devicePixelRatio,
'screenHeight': size.height * devicePixelRatio,
'windowWidth': size.width,
'windowHeight': size.height,
'statusBarHeight': viewPadding.top,
'screenTop': 0, // Flutter中通常不使用此值,设为0
'safeArea': {
'left': safeAreaLeft,
'right': safeAreaRight,
'top': safeAreaTop,
'bottom': safeAreaBottom,
'width': safeAreaWidth,
'height': safeAreaHeight,
},
};
await getIt.get<SharedPreferences>().setString('windowInfo', jsonEncode(windowInfo));
}
}
@override
Widget build(BuildContext context) {
final Map<String, dynamic>? extraData = GoRouterState.of(context).extra as Map<String, dynamic>?;
print("接收到的参数: $extraData");
var ip = extraData?['ip'] ?? '127.0.0.1';
var sessionCode = extraData?['sessionCode'];
var userCode = extraData?['userCode'];
var classCode = extraData?['classCode'];
var userType = extraData?['userType'];
var stuId = extraData?['stuId'];
print("sessionCode:$sessionCode");
return BlocProvider(
create: (context) =>
WebCubit(WebState(false, '界面加载中...', false, sessionCode, userCode, classCode, userType, stuId)),
create: (context) => WebCubit(
WebState(
ip: ip,
sessionCode: sessionCode,
userCode: userCode,
classCode: classCode,
userType: userType,
stuId: stuId,
),
),
child: BlocConsumer<WebCubit, WebState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text(state.title)),
body: state.loaded
? WebViewWidget(controller: context.read<WebCubit>().controller)
: const Center(child: CircularProgressIndicator()),
// 用于测试一下点击跳转路由
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
context.read<WebCubit>().handleBack();
},
child: Scaffold(
appBar: AppBar(title: Text(state.title)),
body: state.loaded
? SizedBox(
height: MediaQuery.of(context).size.height - 120, // 减去100像素留空
child: WebViewWidget(controller: context.read<WebCubit>().controller),
)
: const Center(child: CircularProgressIndicator()),
// 用于测试一下点击跳转路由
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: "btn1",
onPressed: () {
context.read<WebCubit>().goAuth();
},
child: const Icon(Icons.add),
),
const SizedBox(height: 16),
FloatingActionButton(
heroTag: "btn2",
onPressed: () {
context.read<WebCubit>().goAuth2();
},
child: const Icon(Icons.add_alarm),
),
]
)
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: "btn1",
onPressed: () {
context.read<WebCubit>().goWechatAuth();
},
child: const Icon(Icons.chat_outlined),
),
const SizedBox(height: 16),
FloatingActionButton(
heroTag: "btn2",
onPressed: () {
context.read<WebCubit>().goAuth();
},
child: const Icon(Icons.accessibility_new),
),
const SizedBox(height: 16),
FloatingActionButton(
heroTag: "btn3",
onPressed: () {
context.read<WebCubit>().goMiniProgram();
},
child: const Icon(Icons.app_blocking_sharp),
),
],
),
),
);
},
listener: (context, state) {
print("web page listener -------------------------");
// 跳转到微信授权页面
if (state.needAuth) {
print("跳转到微信授权页面");
context.go("/wechatAuth");
}
if (!state.recorderIsInit) {}
},
),
);
}
}
// class WebPage extends StatelessWidget {
// const WebPage({super.key});
//
// @override
// Widget build(BuildContext context) {
// final Map<String, dynamic>? extraData = GoRouterState.of(context).extra as Map<String, dynamic>?;
//
// var ip = extraData?['ip'] ?? '127.0.0.1';
// var sessionCode = extraData?['sessionCode'];
// var userCode = extraData?['userCode'];
// var classCode = extraData?['classCode'];
// var userType = extraData?['userType'];
// var stuId = extraData?['stuId'];
//
// return BlocProvider(
// create: (context) => WebCubit(
// WebState(
// ip: ip,
// sessionCode: sessionCode,
// userCode: userCode,
// classCode: classCode,
// userType: userType,
// stuId: stuId,
// ),
// ),
// child: BlocConsumer<WebCubit, WebState>(
// builder: (context, state) {
// return PopScope(
// canPop: false,
// onPopInvokedWithResult: (didPop, result) {
// context.read<WebCubit>().handleBack();
// },
// child: Scaffold(
// appBar: AppBar(title: Text(state.title)),
// body: state.loaded
// ? SizedBox(
// height: MediaQuery.of(context).size.height - 120, // 减去100像素留空
// child: WebViewWidget(controller: context.read<WebCubit>().controller),
// )
// : const Center(child: CircularProgressIndicator()),
// // 用于测试一下点击跳转路由
// floatingActionButton: Column(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// FloatingActionButton(
// heroTag: "btn1",
// onPressed: () {
// context.read<WebCubit>().goWechatAuth();
// },
// child: const Icon(Icons.chat_outlined),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn2",
// onPressed: () {
// context.read<WebCubit>().goAuth();
// },
// child: const Icon(Icons.accessibility_new),
// ),
// const SizedBox(height: 16),
// FloatingActionButton(
// heroTag: "btn3",
// onPressed: () {
// context.read<WebCubit>().goMiniProgram();
// },
// child: const Icon(Icons.app_blocking_sharp),
// ),
// ],
// ),
// ),
// );
// },
// listener: (context, state) {
// if(!state.recorderIsInit) {
//
// }
// },
// ),
// );
// }
//
//
// }
import 'package:appframe/bloc/wechat_auth_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class WechatAuthPage extends StatelessWidget {
const WechatAuthPage({super.key});
......@@ -9,49 +8,43 @@ class WechatAuthPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WechatAuthCubit(WechatAuthState(null, null, null, null, null, 'no msg!')),
create: (context) => WechatAuthCubit(WechatAuthState()),
child: BlocConsumer<WechatAuthCubit, WechatAuthState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text('微信授权')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(state.result ?? 'no msg!'),
ElevatedButton(
onPressed: () {
context.read<WechatAuthCubit>().auth();
},
child: const Text('授权'),
),
],
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
context.read<WechatAuthCubit>().goIndex();
},
child: Scaffold(
appBar: AppBar(title: Text('微信授权')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 200,
child: TextField(
controller: context.read<WechatAuthCubit>().textEditingController,
decoration: InputDecoration(hintText: '请输入UI端IP', border: OutlineInputBorder()),
),
),
SizedBox(height: 20),
Text(state.result ?? '点击拉取微信授权'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
context.read<WechatAuthCubit>().auth();
},
child: const Text('微信授权'),
),
],
),
),
),
);
},
listener: (context, state) {
print("wechat auth page listener-------------------------");
print(state.sessionCode);
print(state.userCode);
print(state.classCode);
print(state.userType);
print(state.stuId);
print(state.result);
print('带参数跳转webview');
context.go(
'/web',
extra: {
"sessionCode": state.sessionCode,
"userCode": state.userCode,
"classCode": state.classCode,
"userType": state.userType,
"stuId": state.stuId,
},
);
},
listener: (context, state) {},
),
);
}
......
import 'package:flutter/material.dart';
import 'package:flutter_sound/public/flutter_sound_recorder.dart';
/// 利用 StatefulWidget 的状态管理,自动清理 FlutterSoundRecorder, 释放资源
class RecorderWidget extends StatefulWidget {
late final FlutterSoundRecorder _recorder;
FlutterSoundRecorder get recorder => _recorder;
RecorderWidget({super.key, required FlutterSoundRecorder recorder}) {
_recorder = recorder;
}
@override
State<StatefulWidget> createState() {
return _RecorderWidgetState();
}
}
class _RecorderWidgetState extends State<RecorderWidget> {
late BuildContext _context;
@override
Widget build(BuildContext context) {
return SizedBox(
);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
print('销毁 RecorderWidget -------------------->');
final widget = _context.widget as RecorderWidget;
try {
widget.recorder.closeRecorder();
} catch (e) {
print(e);
}
super.dispose();
}
}
......@@ -29,4 +29,39 @@ class FileTypeUtil {
await randomAccessFile.close();
}
}
}
/// 根据MIME类型获取常见的文件扩展名
static String getExtensionFromMime(String? mimeType) {
if (mimeType == null) return '';
// 常见视频MIME类型到扩展名的映射
const videoMimeToExtension = {
'video/mp4': 'mp4',
'video/avi': 'avi',
'video/quicktime': 'mov',
'video/x-matroska': 'mkv',
'video/webm': 'webm',
'video/3gpp': '3gp',
'video/3gpp2': '3g2',
'video/x-msvideo': 'avi',
'video/x-ms-wmv': 'wmv',
'video/mpeg': 'mpg',
'video/ogg': 'ogv',
};
// 常见图片MIME类型到扩展名的映射
const imageMimeToExtension = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
'image/bmp': 'bmp',
'image/svg+xml': 'svg',
'image/tiff': 'tiff',
};
return videoMimeToExtension[mimeType] ??
imageMimeToExtension[mimeType] ??
mimeType.split('/').last;
}
}
\ No newline at end of file
import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'package:video_compress/video_compress.dart';
/// 缩略图工具类
///
......@@ -28,20 +28,28 @@ class ThumbnailUtil {
/// 返回缩略图路径
static Future<String?> genVideoThumbnail(String videoPath, Directory dir) async {
try {
final thumbnailPath = '${dir.path}/video_thumb_${DateTime.now().millisecondsSinceEpoch}.jpg';
final thumbPath = await VideoThumbnail.thumbnailFile(
video: videoPath,
thumbnailPath: thumbnailPath,
imageFormat: ImageFormat.JPEG,
maxWidth: 128, // 缩略图最大宽度
quality: 75, // 图片质量
);
return thumbPath;
var fileThumbnail = await VideoCompress.getFileThumbnail(videoPath, quality: 50, position: -1);
return fileThumbnail.path;
} catch (e) {
print('生成视频缩略图出错: $e');
return null;
}
// try {
// final thumbnailPath = '${dir.path}/video_thumb_${DateTime.now().millisecondsSinceEpoch}.jpg';
//
// final thumbPath = await VideoThumbnail.thumbnailFile(
// video: videoPath,
// thumbnailPath: thumbnailPath,
// imageFormat: ImageFormat.JPEG,
// maxWidth: 128, // 缩略图最大宽度
// quality: 75, // 图片质量
// );
//
// return thumbPath;
// } catch (e) {
// print('生成视频缩略图出错: $e');
// return null;
// }
}
}
\ No newline at end of file
}
......@@ -5,16 +5,32 @@
import FlutterMacOS
import Foundation
import connectivity_plus
import device_info_plus
import file_picker
import file_selector_macos
import flutter_image_compress_macos
import geolocator_apple
import mobile_scanner
import network_info_plus
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
import video_compress
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
}
......@@ -145,6 +145,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.19.1"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
convert:
dependency: transitive
description:
......@@ -233,6 +249,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.7"
exif:
dependency: "direct main"
description:
name: exif
sha256: a7980fdb3b7ffcd0b035e5b8a5e1eef7cadfe90ea6a4e85ebb62f87b96c7a172
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.0"
fake_async:
dependency: transitive
description:
......@@ -440,6 +464,70 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.9"
geoclue:
dependency: transitive
description:
name: geoclue
sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.1"
geolocator:
dependency: "direct main"
description:
name: geolocator
sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
url: "https://pub.flutter-io.cn"
source: hosted
version: "14.0.2"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.2"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.13"
geolocator_linux:
dependency: transitive
description:
name: geolocator_linux
sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.3"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.6"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.3"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.5"
get_it:
dependency: "direct main"
description:
......@@ -472,6 +560,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
gsettings:
dependency: transitive
description:
name: gsettings
sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.8"
hashcodes:
dependency: transitive
description:
......@@ -680,6 +776,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.1"
nested:
dependency: transitive
description:
......@@ -688,6 +792,30 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
network_info_plus:
dependency: "direct main"
description:
name: network_info_plus
sha256: "2866dadcbee2709e20d67737a1556f5675b8b0cdcf2c1659ba74bc21bffede4f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
network_info_plus_platform_interface:
dependency: transitive
description:
name: network_info_plus_platform_interface
sha256: "7e7496a8a9d8136859b8881affc613c4a21304afeb6c324bcefc4bd0aff6b94b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.2"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.0"
package_config:
dependency: transitive
description:
......@@ -696,6 +824,22 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
package_info_plus:
dependency: transitive
description:
name: package_info_plus
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.3.1"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
path:
dependency: "direct main"
description:
......@@ -965,6 +1109,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
......@@ -1029,6 +1181,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.5.1"
vector_math:
dependency: transitive
description:
......@@ -1037,14 +1197,30 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
video_thumbnail:
vibration:
dependency: "direct main"
description:
name: vibration
sha256: "1fd51cb0f91c6d512734ca0e282dd87fbc7f389b6da5f03c77709ba2cf8fa901"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
vibration_platform_interface:
dependency: transitive
description:
name: vibration_platform_interface
sha256: "4134fbfcd427b59a7a91f8733292e4e9b29a7f1e8224ff0d80f5745fbf0743c6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.1"
video_compress:
dependency: "direct main"
description:
name: video_thumbnail
sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b"
name: video_compress
sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.6"
version: "3.1.4"
vm_service:
dependency: transitive
description:
......
......@@ -30,28 +30,35 @@ environment:
dependencies:
flutter:
sdk: flutter
fluwx: ^5.7.2
go_router: ^16.2.1
flutter_bloc: ^9.1.1
webview_flutter: ^4.13.0
permission_handler: ^12.0.1
path: ^1.9.1
path_provider: ^2.1.5
file_picker: ^10.3.2
image_picker: ^1.2.0
equatable: ^2.0.7
dio: ^5.9.0
archive: ^4.0.7
get_it: ^8.2.0
json_annotation: ^4.9.0
shared_preferences: ^2.5.3
flutter_sound: ^9.28.0
connectivity_plus: ^7.0.0
device_info_plus: ^11.5.0
gallery_saver_plus: ^3.2.9
dio: ^5.9.0
equatable: ^2.0.7
exif: ^3.3.0
file_picker: ^10.3.2
flutter_bloc: ^9.1.1
flutter_image_compress: ^2.4.0
flutter_sound: ^9.28.0
fluwx: ^5.7.2
gallery_saver_plus: ^3.2.9
get_it: ^8.2.0
geolocator: ^14.0.2
go_router: ^16.2.1
image_picker: ^1.2.0
image_size_getter: ^2.4.1
video_thumbnail: ^0.5.6
json_annotation: ^4.9.0
mime: ^2.0.0
mobile_scanner: ^7.0.1
network_info_plus: ^7.0.0
path: ^1.9.1
path_provider: ^2.1.5
permission_handler: ^12.0.1
shared_preferences: ^2.5.3
uuid: ^4.5.1
vibration: ^3.1.3
video_compress: ^3.1.4
webview_flutter: ^4.13.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
......
......@@ -6,12 +6,18 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <geolocator_windows/geolocator_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
}
......@@ -3,7 +3,9 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
file_selector_windows
geolocator_windows
permission_handler_windows
)
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!