Commit e54b285c by Administrator

md

2 parents fe88459c b94ae0b4
Showing 62 changed files with 2062 additions and 1170 deletions
...@@ -38,6 +38,11 @@ android { ...@@ -38,6 +38,11 @@ android {
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
manifestPlaceholders["VIVO_APPKEY"] = "f7e37b20f48234c425d9e7f82fdd16c0"
manifestPlaceholders["VIVO_APPID"] = "105993977"
// manifestPlaceholders["HONOR_APPID"] = "您应用分配的证书 APPID"
} }
// 添加签名配置 // 添加签名配置
...@@ -84,3 +89,23 @@ android { ...@@ -84,3 +89,23 @@ android {
flutter { flutter {
source = "../.." source = "../.."
} }
dependencies {
// implementation("com.tencent.timpush:timpush:8.7.7201")
// implementation("com.tencent.liteav.tuikit:tuicore:8.7.7201")
// 版本号 "VERSION" 请前往 更新日志 中获取配置。
// Huawei
//implementation("com.tencent.timpush:huawei:8.7.7201")
// XiaoMi
//implementation("com.tencent.timpush:xiaomi:8.7.7201")
// OPPO
//implementation("com.tencent.timpush:oppo:8.7.7201")
// vivo
implementation("com.tencent.timpush:vivo:8.7.7201")
// Honor
//implementation("com.tencent.timpush:honor:8.7.7201")
// Meizu
//implementation("com.tencent.timpush:meizu:8.7.7201")
// Google Firebase Cloud Messaging (Google FCM)
//implementation("com.tencent.timpush:fcm:8.7.7201")
}
\ No newline at end of file \ No newline at end of file
...@@ -16,5 +16,13 @@ ...@@ -16,5 +16,13 @@
*; *;
} }
# tencent push
-keep class com.tencent.qcloud.** {
*;
}
-keep class com.tencent.timpush.** {
*;
}
# 可选:如果遇到兼容性问题,可以忽略警告 # 可选:如果遇到兼容性问题,可以忽略警告
-dontwarn com.tencent.mm.** -dontwarn com.tencent.mm.**
\ No newline at end of file \ No newline at end of file
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
<application <application
android:label="班小二" android:label="班小二"
android:name="${applicationName}" android:name="cn.banxe.bxe.MyApplication"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/launcher_icon"
android:networkSecurityConfig="@xml/network_security_config"> android:networkSecurityConfig="@xml/network_security_config">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
......
package cn.banxe.bxe;
import com.tencent.chat.flutter.push.tencent_cloud_chat_push.application.TencentCloudChatPushApplication;
public class MyApplication extends TencentCloudChatPushApplication {
@Override
public void onCreate() {
super.onCreate();
}
}
\ No newline at end of file \ No newline at end of file
No preview for this file type
...@@ -13,16 +13,16 @@ ...@@ -13,16 +13,16 @@
<button onclick="startRecord()">开始录音</button> <button onclick="startRecord()">开始录音</button>
<button onclick="">暂停录音</button> <button onclick="pauseRecord()">暂停录音</button>
<button onclick="">唤醒录音</button> <button onclick="resumeRecord()">唤醒录音</button>
<button onclick="stopRecord()">停止录音</button> <button onclick="stopRecord()">停止录音</button>
<br> <br>
<br> <br>
<input type="hidden" id="url" value=""> <input type="hidden" id="url" value="">
<button onclick="startPlay()">开始播放</button> <button onclick="startPlay()">开始播放</button>
<button onclick="">暂停播放</button> <button onclick="pausePlay()">暂停播放</button>
<button onclick="">唤醒播放</button> <button onclick="resumePlay()">唤醒播放</button>
<button onclick="stopPlay()">停止播放</button> <button onclick="stopPlay()">停止播放</button>
<button onclick="openlink()">打开新链接</button> <button onclick="openlink()">打开新链接</button>
...@@ -36,16 +36,28 @@ ...@@ -36,16 +36,28 @@
<script> <script>
function xeJsBridgeCallback(message) { function xeJsBridgeCallback(message) {
let jsonData = JSON.parse(message); document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + message + '</p>';
document.getElementById('resp').innerHTML = '<p><strong>响应:</strong> ' + jsonData.data + '</p>';
// 设置id为url的input的值 // 设置id为url的input的值
document.getElementById('url').value = jsonData.data; let jsonData = JSON.parse(message);
if(jsonData.cmd=="audioRecorderStop"){
document.getElementById('url').value = jsonData.data.tempFilePath;
}
} }
function startRecord() { function startRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderStart", "params": {} }'; let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderStart", "params": {"duration":3} }';
xeJsBridge.postMessage(message);
}
function pauseRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderPause", "params": {} }';
xeJsBridge.postMessage(message);
}
function resumeRecord() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioRecorderResume", "params": {} }';
xeJsBridge.postMessage(message); xeJsBridge.postMessage(message);
} }
...@@ -57,10 +69,21 @@ ...@@ -57,10 +69,21 @@
function startPlay() { function startPlay() {
let url = document.getElementById('url').value; let url = document.getElementById('url').value;
//let url = 'https://files-cos.banxiaoer.net/txbb/res/mp3/d2-d560e952-4029-11eb-928a-acde48001122/wd-db0b7502-4029-11eb-928a-acde48001122_h.mp3';
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioPlay", "params": {"url":"' + url + '"} }'; let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioPlay", "params": {"url":"' + url + '"} }';
xeJsBridge.postMessage(message); xeJsBridge.postMessage(message);
} }
function pausePlay() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioPause", "params": {} }';
xeJsBridge.postMessage(message);
}
function resumePlay() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioResume", "params": {} }';
xeJsBridge.postMessage(message);
}
function stopPlay() { function stopPlay() {
let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioStop", "params": {} }'; let message = '{ "timestamp": 1, "unique": "123", "cmd": "audioStop", "params": {} }';
xeJsBridge.postMessage(message); xeJsBridge.postMessage(message);
......
{"version":"1.0.1","cn.banxe.bxe":{"manifestPlaceholders":{"VIVO_APPKEY":"f7e37b20f48234c425d9e7f82fdd16c0","VIVO_APPID":"105993977","HONOR_APPID":""},"vivoPushBussinessId":"44580"}}
\ No newline at end of file \ No newline at end of file

295 Bytes | W: | H:

416 Bytes | W: | H:

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  • 2-up
  • Swipe
  • Onion skin

406 Bytes | W: | H:

869 Bytes | W: | H:

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  • 2-up
  • Swipe
  • Onion skin

282 Bytes | W: | H:

607 Bytes | W: | H:

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  • 2-up
  • Swipe
  • Onion skin

406 Bytes | W: | H:

869 Bytes | W: | H:

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  • 2-up
  • Swipe
  • Onion skin
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/services/im_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
class ImState extends Equatable {
final List<String> messages;
const ImState({this.messages = const []});
ImState copyWith({List<String>? messages}) {
return ImState(
messages: messages ?? this.messages,
);
}
@override
List<Object?> get props => [messages];
}
class ImCubit extends Cubit<ImState> {
late ImService imService;
late V2TimAdvancedMsgListener _msgListener;
ImCubit() : super(const ImState()) {
imService = getIt.get<ImService>();
_msgListener = V2TimAdvancedMsgListener(
onRecvNewMessage: _onMessageReceived,
);
init();
}
void init() async {
await imService.removeMsgListener(imService.msgListener);
await imService.addMsgListener(_msgListener);
}
void addMessage(String message) {
emit(state.copyWith(messages: [...state.messages, message]));
}
void goWeb() {
router.go('/web');
}
Future<void> _onMessageReceived(V2TimMessage message) async {
// 处理文本消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) {
print("page 收到IM消息-------- ${message.textElem?.text}");
// 处理接收到的消息
emit(state.copyWith(messages: [...state.messages, message.textElem?.text ?? '']));
} else {
print("page 忽略非文本消息-------- ${message.textElem?.text}");
}
}
@override
Future<void> close() async {
await imService.removeMsgListener(_msgListener);
await imService.addMsgListener(imService.msgListener);
super.close();
}
}
import 'package:appframe/config/constant.dart'; import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart'; import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/im_service.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../config/locator.dart';
class LoginMainState extends Equatable { class LoginMainState extends Equatable {
final bool agreed; final bool agreed;
...@@ -31,11 +32,13 @@ class LoginMainState extends Equatable { ...@@ -31,11 +32,13 @@ class LoginMainState extends Equatable {
class LoginMainCubit extends Cubit<LoginMainState> { class LoginMainCubit extends Cubit<LoginMainState> {
late final Fluwx _fluwx; late final Fluwx _fluwx;
late final WechatAuthRepository _wechatAuthRepository; late final WechatAuthRepository _wechatAuthRepository;
late final ImService _imService;
LoginMainCubit(super.initialState) { LoginMainCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>(); _fluwx = getIt.get<Fluwx>();
_fluwx.addSubscriber(_responseListener); _fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt<WechatAuthRepository>(); _wechatAuthRepository = getIt<WechatAuthRepository>();
_imService = getIt.get<ImService>();
} }
void toggleAgreed(bool value) { void toggleAgreed(bool value) {
...@@ -95,6 +98,17 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -95,6 +98,17 @@ class LoginMainCubit extends Cubit<LoginMainState> {
sharedPreferences.setString('auth_stuId', stuId ?? ''); sharedPreferences.setString('auth_stuId', stuId ?? '');
sharedPreferences.setString('auth_ip', Constant.h5Server); sharedPreferences.setString('auth_ip', Constant.h5Server);
if(Constant.needIM){
// IM登录, 正式使用时,需要从服务端获取用户签名
var loginResult = await _imService.login(userCode);
if (loginResult) {
print("微信登录处,IM 登录成功");
await _imService.registerPush();
} else {
print("微信登录处,IM 登录失败");
}
}
router.go( router.go(
'/web', '/web',
extra: { extra: {
...@@ -108,4 +122,10 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -108,4 +122,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
); );
} }
} }
@override
Future<void> close() {
_fluwx.removeSubscriber(_responseListener);
return super.close();
}
} }
import 'package:appframe/config/db.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/services/mqtt_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mqtt_client/mqtt_client.dart';
class MqttState extends Equatable {
final List<String> messages;
const MqttState({this.messages = const []});
MqttState copyWith({List<String>? messages}) {
return MqttState(
messages: messages ?? this.messages,
);
}
@override
List<Object?> get props => [messages];
}
class MqttCubit extends Cubit<MqttState> with WidgetsBindingObserver {
late MqttService _mqttService;
MqttCubit() : super(MqttState()) {
_mqttService = getIt.get<MqttService>();
_mqttService.listen(_onMessageReceived);
WidgetsBinding.instance.addObserver(this);
}
void goWeb() {
router.go('/web');
}
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
// print('----------------------didChangeAppLifecycleState------------------------------');
if (state == AppLifecycleState.resumed) {
// print('----------------------resumed------------------------------');
if (!_mqttService.isConnected()) {
// print('----------------------reconnected------------------------------');
await _mqttService.initConn();
_mqttService.listen(_onMessageReceived);
}
}
}
Future<void> _onMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) async {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('-------------------------> $pt');
// Map<String, dynamic> data = {"id": 1, "content": pt};
// bxeDb.insert('msg', data);
//
// var msgList = await bxeDb.query('msg');
// for (var msg in msgList) {
// print('-------------------------> $msg');
// }
// 处理接收到的消息
emit(state.copyWith(messages: [...state.messages, pt]));
}
@override
Future<void> close() {
// 关闭时移除观察者
WidgetsBinding.instance.removeObserver(this);
return super.close();
}
}
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();
}
}
...@@ -7,26 +7,27 @@ import 'package:appframe/config/locator.dart'; ...@@ -7,26 +7,27 @@ import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/models/message/h5_message.dart'; import 'package:appframe/data/models/message/h5_message.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/im_service.dart';
import 'package:appframe/services/local_server_service.dart'; import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/utils/audio_util.dart'; import 'package:appframe/services/player_service.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart'; import 'package:wechat_camera_picker/wechat_camera_picker.dart';
class WebState extends Equatable { class WebState extends Equatable {
final int selectedIndex;
final bool loaded; final bool loaded;
final String title; final String title;
final bool beBack; final int titleColor;
final int bgColor;
final String opIcon;
final bool showBottomNavBar;
final String? ip; final String? ip;
final String? sessionCode; final String? sessionCode;
...@@ -36,14 +37,14 @@ class WebState extends Equatable { ...@@ -36,14 +37,14 @@ class WebState extends Equatable {
final String? stuId; final String? stuId;
/// 录音 /// 录音
final bool recorderIsInit; // final bool recorderIsInit;
final int recordState; //final int recordState;
final String recordPath; // final String recordPath;
/// 播放 /// 播放
final bool playerIsInit; // final bool playerIsInit;
final int playState; //final int playState;
final String playId; // final String playId;
/// getOrientationCmd /// getOrientationCmd
final bool orientationCmdFlag; final bool orientationCmdFlag;
...@@ -62,21 +63,25 @@ class WebState extends Equatable { ...@@ -62,21 +63,25 @@ class WebState extends Equatable {
final String chooseVideoCmdMessage; final String chooseVideoCmdMessage;
const WebState({ const WebState({
this.selectedIndex = 0,
this.loaded = false, this.loaded = false,
this.title = '界面加载中...', this.title = '界面加载中...',
this.beBack = false, this.titleColor = 0xFFFFFFFF,
this.bgColor = 0xFF7691FA,
this.opIcon = 'none',
this.showBottomNavBar = false,
this.ip, this.ip,
this.sessionCode, this.sessionCode,
this.userCode, this.userCode,
this.classCode, this.classCode,
this.userType, this.userType,
this.stuId, this.stuId,
this.recorderIsInit = false, // this.recorderIsInit = false,
this.recordState = 0, // this.recordState = 0,
this.recordPath = '', // this.recordPath = '',
this.playerIsInit = false, // this.playerIsInit = false,
this.playState = 0, // this.playState = 0,
this.playId = '', // this.playId = '',
this.orientationCmdFlag = false, this.orientationCmdFlag = false,
this.orientationCmdMessage = '', this.orientationCmdMessage = '',
this.windowInfoCmdFlag = false, this.windowInfoCmdFlag = false,
...@@ -88,21 +93,26 @@ class WebState extends Equatable { ...@@ -88,21 +93,26 @@ class WebState extends Equatable {
}); });
WebState copyWith({ WebState copyWith({
int? selectedIndex,
bool? loaded, bool? loaded,
String? title, String? title,
bool? beBack, int? titleColor,
int? bgColor,
String? opIcon,
bool? showNavBar,
bool? showBottomNavBar,
String? ip, String? ip,
String? sessionCode, String? sessionCode,
String? userCode, String? userCode,
String? classCode, String? classCode,
int? userType, int? userType,
String? stuId, String? stuId,
bool? recorderIsInit, // bool? recorderIsInit,
int? recordState, // int? recordState,
String? recordPath, // String? recordPath,
bool? playerIsInit, // bool? playerIsInit,
int? playState, // int? playState,
String? playId, // String? playId,
bool? orientationCmdFlag, bool? orientationCmdFlag,
String? orientationCmdMessage, String? orientationCmdMessage,
bool? windowInfoCmdFlag, bool? windowInfoCmdFlag,
...@@ -113,21 +123,25 @@ class WebState extends Equatable { ...@@ -113,21 +123,25 @@ class WebState extends Equatable {
String? chooseVideoCmdMessage, String? chooseVideoCmdMessage,
}) { }) {
return WebState( return WebState(
selectedIndex: selectedIndex ?? this.selectedIndex,
loaded: loaded ?? this.loaded, loaded: loaded ?? this.loaded,
title: title ?? this.title, title: title ?? this.title,
beBack: beBack ?? this.beBack, titleColor: titleColor ?? this.titleColor,
bgColor: bgColor ?? this.bgColor,
opIcon: opIcon ?? this.opIcon,
showBottomNavBar: showBottomNavBar ?? this.showBottomNavBar,
ip: ip ?? this.ip, ip: ip ?? this.ip,
sessionCode: sessionCode ?? this.sessionCode, sessionCode: sessionCode ?? this.sessionCode,
userCode: userCode ?? this.userCode, userCode: userCode ?? this.userCode,
classCode: classCode ?? this.classCode, classCode: classCode ?? this.classCode,
userType: userType ?? this.userType, userType: userType ?? this.userType,
stuId: stuId ?? this.stuId, stuId: stuId ?? this.stuId,
recorderIsInit: recorderIsInit ?? this.recorderIsInit, // recorderIsInit: recorderIsInit ?? this.recorderIsInit,
recordState: recordState ?? this.recordState, // recordState: recordState ?? this.recordState,
recordPath: recordPath ?? this.recordPath, // recordPath: recordPath ?? this.recordPath,
playerIsInit: playerIsInit ?? this.playerIsInit, // playerIsInit: playerIsInit ?? this.playerIsInit,
playState: playState ?? this.playState, // playState: playState ?? this.playState,
playId: playId ?? this.playId, // playId: playId ?? this.playId,
orientationCmdFlag: orientationCmdFlag ?? this.orientationCmdFlag, orientationCmdFlag: orientationCmdFlag ?? this.orientationCmdFlag,
orientationCmdMessage: orientationCmdMessage ?? this.orientationCmdMessage, orientationCmdMessage: orientationCmdMessage ?? this.orientationCmdMessage,
windowInfoCmdFlag: windowInfoCmdFlag ?? this.windowInfoCmdFlag, windowInfoCmdFlag: windowInfoCmdFlag ?? this.windowInfoCmdFlag,
...@@ -141,21 +155,25 @@ class WebState extends Equatable { ...@@ -141,21 +155,25 @@ class WebState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
selectedIndex,
loaded, loaded,
title, title,
beBack, titleColor,
bgColor,
opIcon,
showBottomNavBar,
ip, ip,
sessionCode, sessionCode,
userCode, userCode,
classCode, classCode,
userType, userType,
stuId, stuId,
recorderIsInit, // recorderIsInit,
recordState, // recordState,
recordPath, // recordPath,
playerIsInit, // playerIsInit,
playState, // playState,
playId, // playId,
orientationCmdFlag, orientationCmdFlag,
orientationCmdMessage, orientationCmdMessage,
windowInfoCmdFlag, windowInfoCmdFlag,
...@@ -172,14 +190,20 @@ class WebCubit extends Cubit<WebState> { ...@@ -172,14 +190,20 @@ class WebCubit extends Cubit<WebState> {
late final WebViewController _controller; late final WebViewController _controller;
late final HttpServer _server; late final HttpServer _server;
late final Fluwx _fluwx; late final Fluwx _fluwx;
FlutterSoundRecorder? _recorder; late final PlayerService _playerService;
FlutterSoundPlayer? _player; late final PlayerService _recorderService;
// FlutterSoundRecorder? _recorder;
// StreamSubscription? _recorderSubscription;
// FlutterSoundPlayer? _player;
// StreamSubscription? _playerSubscription;
// int? _playDuration;
WebViewController get controller => _controller; WebViewController get controller => _controller;
FlutterSoundRecorder? get recorder => _recorder; // FlutterSoundRecorder? get recorder => _recorder;
FlutterSoundPlayer? get player => _player; // FlutterSoundPlayer? get player => _player;
WebCubit(super.initialState) { WebCubit(super.initialState) {
// 消息处理器 // 消息处理器
...@@ -195,6 +219,8 @@ class WebCubit extends Cubit<WebState> { ...@@ -195,6 +219,8 @@ class WebCubit extends Cubit<WebState> {
// 进行新页面加载时,关闭录音器和播放器,(如果有打开过) // 进行新页面加载时,关闭录音器和播放器,(如果有打开过)
// closeLocalRecorder(); // closeLocalRecorder();
// closeLocalPlayer(); // closeLocalPlayer();
await _playerService.close();
await _recorderService.close();
}, },
onPageFinished: (String url) async { onPageFinished: (String url) async {
print('onPageFinished--------------------------------->'); print('onPageFinished--------------------------------->');
...@@ -205,8 +231,8 @@ class WebCubit extends Cubit<WebState> { ...@@ -205,8 +231,8 @@ class WebCubit extends Cubit<WebState> {
} }
// 页面加载完成时,清空录音和音频(如果有打开过) // 页面加载完成时,清空录音和音频(如果有打开过)
await clearRecording(); // await clearRecording();
await clearAudio(); // await clearAudio();
_controller.runJavaScript( _controller.runJavaScript(
'document.querySelector("meta[name=viewport]").setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")', 'document.querySelector("meta[name=viewport]").setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")',
...@@ -221,20 +247,36 @@ class WebCubit extends Cubit<WebState> { ...@@ -221,20 +247,36 @@ class WebCubit extends Cubit<WebState> {
_startLocalServerAndLoadHtml(); _startLocalServerAndLoadHtml();
_fluwx = getIt.get<Fluwx>(); _fluwx = getIt.get<Fluwx>();
_playerService = getIt.get<PlayerService>();
_playerService.sendResponse = _sendResponse;
_recorderService = getIt.get<PlayerService>();
} }
_startLocalServerAndLoadHtml() async { void _startLocalServerAndLoadHtml() async {
// 启动本地服务器 // 启动本地服务器
_server = await LocalServerService().startLocalServer(); _server = await getIt.get<LocalServerService>().startLocalServer();
final String serverUrl; final String serverUrl;
if (state.sessionCode == null || state.sessionCode == '') { if (state.sessionCode == null || state.sessionCode == '') {
serverUrl = '${Constant.localServerUrl}/index.html'; // serverUrl = '${Constant.localServerUrl}/index.html';
// serverUrl = '${Constant.localServerTestFileUrl}/login.html'; serverUrl = '${Constant.localServerTestFileUrl}/login.html';
// serverUrl = '${Constant.localServerTestFileUrl}/test2.html'; // serverUrl = '${Constant.localServerTestFileUrl}/test2.html';
} else { } else {
// serverUrl = // serverUrl =
// 'http://${state.ip}:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}'; // 'http://${state.ip}:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
// IM 登录
if (Constant.needIM) {
var imService = getIt.get<ImService>();
var loginResult = await imService.login(state.userCode!);
if (loginResult) {
print("缓存自动登录处,IM 登录成功");
await imService.registerPush();
} else {
print("缓存自动登录处,IM 登录失败");
}
}
serverUrl = serverUrl =
'${Constant.localServerUrl}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}'; '${Constant.localServerUrl}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
} }
...@@ -242,7 +284,7 @@ class WebCubit extends Cubit<WebState> { ...@@ -242,7 +284,7 @@ class WebCubit extends Cubit<WebState> {
_controller.loadRequest(Uri.parse(serverUrl)); _controller.loadRequest(Uri.parse(serverUrl));
} }
_onMessageReceived(JavaScriptMessage message) async { void _onMessageReceived(JavaScriptMessage message) async {
try { try {
_dispatcher.dispatch(message.message, (response) { _dispatcher.dispatch(message.message, (response) {
_sendResponse(response); _sendResponse(response);
...@@ -261,7 +303,8 @@ class WebCubit extends Cubit<WebState> { ...@@ -261,7 +303,8 @@ class WebCubit extends Cubit<WebState> {
} }
void finishLoading() { void finishLoading() {
emit(state.copyWith(loaded: true, title: '班小二测试', beBack: true)); // emit(state.copyWith(loaded: true, title: '班小二测试', opIcon: 'none'));
emit(state.copyWith(loaded: true, title: '班小二测试'));
} }
// 测试 // 测试
...@@ -278,8 +321,8 @@ class WebCubit extends Cubit<WebState> { ...@@ -278,8 +321,8 @@ class WebCubit extends Cubit<WebState> {
router.go('/loginMain'); router.go('/loginMain');
} }
void goMqtt(){ void goIm() {
router.go('/mqtt'); router.go('/im');
} }
//测试 //测试
...@@ -300,6 +343,8 @@ class WebCubit extends Cubit<WebState> { ...@@ -300,6 +343,8 @@ class WebCubit extends Cubit<WebState> {
miniProgramType: WXMiniProgramType.preview, miniProgramType: WXMiniProgramType.preview,
), ),
); );
// _fluwx.share(WeChatShareTextModel("source text", scene: WeChatScene.session));
} }
// void _responseListener(response) { // void _responseListener(response) {
...@@ -320,14 +365,44 @@ class WebCubit extends Cubit<WebState> { ...@@ -320,14 +365,44 @@ class WebCubit extends Cubit<WebState> {
_sendResponse(resp); _sendResponse(resp);
} }
bool setTitle(String title, bool beBack) { Future<void> handleHome() async {
emit(state.copyWith(title: title, beBack: beBack)); // navigateHome指令
var resp = {'unique': '', 'cmd': 'navigateHome', 'data': '', 'errMsg': ''};
_sendResponse(resp);
}
Future<void> handleRefreshPage() async {
// refreshPage指令
var resp = {'unique': '', 'cmd': 'refreshPage', 'data': '', 'errMsg': ''};
_sendResponse(resp);
}
bool setTitleBar(String title, String color, String bgColor, String icon) {
int parsedTitleColor = _hexStringToInt(color);
int parsedBgColor = _hexStringToInt(bgColor);
emit(state.copyWith(title: title, titleColor: parsedTitleColor, bgColor: parsedBgColor, opIcon: icon));
return true; return true;
} }
int _hexStringToInt(String hexString) {
// 移除可能存在的 # 前缀
if (hexString.startsWith('#')) {
hexString = hexString.substring(1);
}
// 确保颜色值是8位(包含alpha通道)
if (hexString.length == 6) {
hexString = 'FF$hexString'; // 添加不透明的alpha值
}
// 解析十六进制字符串为整数
return int.parse(hexString, radix: 16);
}
Future<void> refresh() async { Future<void> refresh() async {
clearRecording(); // await clearRecording();
clearAudio(); // await clearAudio();
_controller.reload(); _controller.reload();
} }
...@@ -335,6 +410,29 @@ class WebCubit extends Cubit<WebState> { ...@@ -335,6 +410,29 @@ class WebCubit extends Cubit<WebState> {
await getIt.get<SharedPreferences>().clear(); await getIt.get<SharedPreferences>().clear();
} }
Future<void> logout() async {
await clearStorage();
// IM 登出
await getIt.get<ImService>().logout();
goLogin();
}
void updateSelectedIndex(int index) {
emit(state.copyWith(selectedIndex: index));
}
void showBottomNavBar() {
emit(state.copyWith(showBottomNavBar: true));
}
void hideBottomNavBar() {
emit(state.copyWith(showBottomNavBar: false));
}
///
///
///
void setChooseImageCmdFlag(bool chooseImageCmdFlag, String chooseImageCmdMessage) { void setChooseImageCmdFlag(bool chooseImageCmdFlag, String chooseImageCmdMessage) {
emit(state.copyWith(chooseImageCmdFlag: chooseImageCmdFlag, chooseImageCmdMessage: chooseImageCmdMessage)); emit(state.copyWith(chooseImageCmdFlag: chooseImageCmdFlag, chooseImageCmdMessage: chooseImageCmdMessage));
} }
...@@ -381,6 +479,7 @@ class WebCubit extends Cubit<WebState> { ...@@ -381,6 +479,7 @@ class WebCubit extends Cubit<WebState> {
requestType: RequestType.image, requestType: RequestType.image,
gridThumbnailSize: const ThumbnailSize.square(80), gridThumbnailSize: const ThumbnailSize.square(80),
previewThumbnailSize: const ThumbnailSize.square(150), previewThumbnailSize: const ThumbnailSize.square(150),
dragToSelect: false,
), ),
); );
...@@ -495,7 +594,11 @@ class WebCubit extends Cubit<WebState> { ...@@ -495,7 +594,11 @@ class WebCubit extends Cubit<WebState> {
void _chooseVideoFromAlbum(BuildContext context, int count, String unique, String cmd) async { void _chooseVideoFromAlbum(BuildContext context, int count, String unique, String cmd) async {
final List<AssetEntity>? result = await AssetPicker.pickAssets( final List<AssetEntity>? result = await AssetPicker.pickAssets(
context, context,
pickerConfig: AssetPickerConfig(maxAssets: count, requestType: RequestType.video), pickerConfig: AssetPickerConfig(
maxAssets: count,
requestType: RequestType.video,
dragToSelect: false,
),
); );
if (result == null || result.isEmpty) { if (result == null || result.isEmpty) {
...@@ -637,231 +740,325 @@ class WebCubit extends Cubit<WebState> { ...@@ -637,231 +740,325 @@ class WebCubit extends Cubit<WebState> {
} }
/// 录音初始化 /// 录音初始化
Future<bool> _initRecorder() async { // Future<bool> _initRecorder(int maxDuration) async {
// 请求麦克风权限 // // 请求麦克风权限
var status = await Permission.microphone.request(); // var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) { // if (status != PermissionStatus.granted) {
throw RecordingPermissionException('no auth'); // throw RecordingPermissionException('no auth');
} // }
//
if (state.recordState != 0) { // // if (state.recordState != 0) {
return false; // // return false;
} // // }
//
final directory = await getTemporaryDirectory(); // final directory = await getTemporaryDirectory();
// String recordPath = '${directory.path}/${Uuid().v5(Namespace.url.value, 'www.banxiaoer.com')}_record.aac'; // // String recordPath = '${directory.path}/${Uuid().v5(Namespace.url.value, 'www.banxiaoer.com')}_record.aac';
// String recordPath = '${directory.path}/${Uuid().v4()}_record.aac'; // // String recordPath = '${directory.path}/${Uuid().v4()}_record.aac';
String recordPath = '${directory.path}/${Uuid().v4()}_record.m4a'; // String recordPath = '${directory.path}/${Uuid().v4()}_record.mp4';
//
// 打开录音器 // // 打开录音器
try { // try {
final recorder = FlutterSoundRecorder(); // final recorder = FlutterSoundRecorder();
_recorder = (await recorder.openRecorder())!; // _recorder = (await recorder.openRecorder())!;
//
emit(state.copyWith(recorderIsInit: true, recordPath: recordPath)); // if (maxDuration > 0) {
return true; // // 设置进度回调间隔
} catch (e) { // await _recorder!.setSubscriptionDuration(Duration(seconds: 1));
throw Exception('打开录音器失败!'); // // 监听录制进度
} // _recorder!.onProgress!.listen((event) {
} // // event.duration 包含当前录制时长
// // event.decibels 包含当前音量级别
/// 开始录音 // print('录制进度: ${event.duration.inSeconds}秒, 音量: ${event.decibels}');
Future<bool> startRecording() async { // /*if (event.duration.inSeconds >= maxDuration) {
if (state.recorderIsInit) { // stopRecording();
return false; // }*/
} // });
// }
if (state.recordState != 0) { //
return false; // emit(state.copyWith(recorderIsInit: true, recordPath: recordPath));
} // return true;
// } catch (e) {
final initResult = await _initRecorder(); // throw Exception('打开录音器失败!');
if (!initResult) { // }
return false; // }
} //
// /// 开始录音
await _recorder!.startRecorder(toFile: state.recordPath, codec: Codec.aacMP4); // Future<bool> startRecording(int maxDuration) async {
emit(state.copyWith(recordState: 1)); // if (state.recorderIsInit) {
return true; // return false;
} // }
//
/// 暂停录音 // // if (state.recordState != 0) {
Future<bool> pauseRecording() async { // // return false;
if (!state.recorderIsInit) { // // }
return false; //
} // if (_recorder != null && !_recorder!.isStopped) {
// return false;
if (state.recordState != 1) { // }
return false; //
} // final initResult = await _initRecorder(maxDuration);
// if (!initResult) {
await _recorder!.pauseRecorder(); // return false;
emit(state.copyWith(recordState: 2)); // }
return true; //
} // await _recorder!.startRecorder(toFile: state.recordPath, codec: Codec.aacMP4);
// // emit(state.copyWith(recordState: 1));
/// 恢复录音 // return true;
Future<bool> resumeRecording() async { // }
if (!state.recorderIsInit) { //
return false; // /// 暂停录音
} // Future<bool> pauseRecording() async {
// if (!state.recorderIsInit) {
if (state.recordState != 2) { // return false;
return false; // }
} //
// // if (state.recordState != 1) {
await _recorder!.resumeRecorder(); // // return false;
emit(state.copyWith(recordState: 1)); // // }
return true; //
} // if (!_recorder!.isRecording) {
// return false;
/// 停止录音 // }
Future<Map<String, dynamic>> stopRecording() async { //
if (!state.recorderIsInit) { // await _recorder!.pauseRecorder();
throw Exception("录音器未初始化"); // // emit(state.copyWith(recordState: 2));
} // return true;
// }
if (state.recordState != 1 && state.recordState != 2) { //
throw Exception("录音器状态错误"); // /// 恢复录音
} // Future<bool> resumeRecording() async {
// if (!state.recorderIsInit) {
var url = await _recorder!.stopRecorder(); // return false;
await _recorder!.closeRecorder(); // }
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: '')); //
// // if (state.recordState != 2) {
if (url == null || url.isEmpty) { // // return false;
throw Exception("录音失败"); // // }
} //
// if (!_recorder!.isPaused) {
var tempDir = await getTemporaryDirectory(); // return false;
String fileName = path.basenameWithoutExtension(url); // }
String mp3Path = '${tempDir.path}/$fileName.mp3'; //
// await _recorder!.resumeRecorder();
// var convertResult = await compute(AudioUtil.convertAacToMp3, {'accPath': url, 'mp3Path': mp3Path}); // // emit(state.copyWith(recordState: 1));
var convertResult = await AudioUtil.convertAacToMp3({'accPath': url, 'mp3Path': mp3Path}); // return true;
if (!convertResult) { // }
throw Exception("录音转码失败"); //
} // /// 停止录音
// Future<Map<String, dynamic>> stopRecording() async {
// 时长 // if (!state.recorderIsInit) {
// var duration = await AudioUtil.getAudioDuration(mp3Path); // throw Exception("录音器未初始化");
var duration = await AudioUtil.getAudioDuration(url); // }
//
return {'tempFilePath': '${Constant.localServerTempFileUrl}$mp3Path', 'duration': duration.inSeconds}; // // if (state.recordState != 1 && state.recordState != 2) {
} // // throw Exception("录音器状态错误");
// // }
/// 清空录音 //
Future<bool> clearRecording() async { // if (!_recorder!.isRecording && !_recorder!.isPaused) {
// await _recorder!.stopRecorder(); // throw Exception("录音器状态错误");
try { // }
await _recorder?.closeRecorder(); //
} catch (e) { // var url = await _recorder!.stopRecorder();
print(e); // await _recorder!.closeRecorder();
} // _recorder = null;
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: '')); // // emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
return true; // emit(state.copyWith(recorderIsInit: false, recordPath: ''));
} //
// if (url == null || url.isEmpty) {
// throw Exception("录音失败");
// }
//
// var tempDir = await getTemporaryDirectory();
// String fileName = path.basenameWithoutExtension(url);
// String mp3Path = '${tempDir.path}/$fileName.mp3';
//
// // var convertResult = await compute(AudioUtil.convertAacToMp3, {'accPath': url, 'mp3Path': mp3Path});
// var convertResult = await AudioUtil.convertAacToMp3({'accPath': url, 'mp3Path': mp3Path});
// if (!convertResult) {
// throw Exception("录音转码失败");
// }
//
// // 时长
// // var duration = await AudioUtil.getAudioDuration(mp3Path);
// var duration = await AudioUtil.getAudioDuration(url);
//
// return {
// 'tempFilePath': '${Constant.localServerTempFileUrl}$mp3Path',
// 'duration': duration.inSeconds,
// };
// }
//
// /// 清空录音
// Future<bool> clearRecording() async {
// // await _recorder!.stopRecorder();
// try {
// await _recorder?.closeRecorder();
// _recorder = null;
// } catch (e) {
// print(e);
// }
// // emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
// emit(state.copyWith(recorderIsInit: false, recordPath: ''));
// return true;
// }
/// 播放初始化 /// 播放初始化
Future<bool> _initPlayer() async { // Future<bool> _initPlayer(String playId) async {
// 打开播放器 // // 打开播放器
try { // try {
final player = FlutterSoundPlayer(); // final player = FlutterSoundPlayer();
_player = (await player.openPlayer())!; // _player = (await player.openPlayer())!;
// _player!.setSpeed(2); // 播放速度,默认1
emit(state.copyWith(playerIsInit: true)); //
return true; // // 播放进度回调
} catch (e) { // _player!.setSubscriptionDuration(Duration(seconds: 1));
throw Exception('打开播放器失败!'); // _playerSubscription = _player!.onProgress!.listen((event) {
} // print('播放回调--------- ${event.duration.inSeconds} ${event.position.inSeconds}');
} //
// _playDuration = event.duration.inSeconds;
/// 播放音频 // var playPosition = event.position.inSeconds;
Future<bool> playAudio(String url) async { //
if (!state.playerIsInit) { // var data = {'playId': state.playId, 'duration': _playDuration, 'currentTime': playPosition};
final initResult = await _initPlayer(); // var h5Cmd = {
if (!initResult) { // 'unique': '',
return false; // 'cmd': 'audioProgress',
} // 'data': data,
} // 'errMsg': '',
// };
await _player!.startPlayer( // _sendResponse(h5Cmd);
fromURI: url, // });
whenFinished: () async { //
await _player!.stopPlayer(); // emit(state.copyWith(playerIsInit: true, playId: playId));
emit(state.copyWith(playState: 0)); // return true;
}, // } catch (e) {
); // throw Exception('打开播放器失败!');
emit(state.copyWith(playState: 1)); // }
return true; // }
} //
// /// 播放音频
/// 暂停播放 // Future<bool> playAudio(String url, int seek, String playId) async {
Future<bool> pauseAudio() async { // if (!state.playerIsInit) {
if (!state.playerIsInit) { // final initResult = await _initPlayer(playId);
throw Exception("播放器未初始化"); // if (!initResult) {
} // return false;
// }
if (state.playState != 1) { // }
throw Exception("播放器状态错误"); //
} // await _player!.startPlayer(
// fromURI: url,
await _player!.pausePlayer(); // whenFinished: () async {
emit(state.copyWith(playState: 2)); // await _player!.stopPlayer();
return true; // // emit(state.copyWith(playState: 0));
} //
// // 补发一下全部进度
/// 恢复播放 // var h5Cmd = {
Future<bool> resumeAudio() async { // 'unique': '',
if (!state.playerIsInit) { // 'cmd': 'audioProgress',
throw Exception("播放器未初始化"); // 'data': {'playId': state.playId, 'duration': _playDuration, 'currentTime': _playDuration},
} // 'errMsg': '',
// };
if (state.playState != 2) { // _sendResponse(h5Cmd);
throw Exception("播放器状态错误"); //
} // // 播放结束后,发送消息给客户端
// h5Cmd = {
await _player!.resumePlayer(); // 'unique': '',
emit(state.copyWith(playState: 1)); // 'cmd': 'audioEnd',
return true; // 'data': {'playId': state.playId},
} // 'errMsg': '',
// };
/// 跳转播放 // _sendResponse(h5Cmd);
Future<bool> seekAudio(int seek) async { // },
if (!state.playerIsInit) { // );
throw Exception("播放器未初始化"); // if (seek != 0) {
} // await seekAudio(seek);
// }
await _player!.seekToPlayer(Duration(seconds: seek)); // // emit(state.copyWith(playState: 1));
emit(state.copyWith(playState: 1)); // return true;
return true; // }
} //
// /// 暂停播放
/// 停止播放 // Future<bool> pauseAudio() async {
Future<bool> stopAudio() async { // if (!state.playerIsInit) {
if (!state.playerIsInit) { // throw Exception("播放器未初始化");
throw Exception("播放器未初始化"); // }
} //
// // if (state.playState != 1) {
if (state.playState != 1 && state.playState != 2) { // // throw Exception("播放器状态错误");
throw Exception("播放器状态错误"); // // }
} //
// if (!_player!.isPlaying) {
await _player!.stopPlayer(); // throw Exception("播放器状态错误");
emit(state.copyWith(playState: 0)); // }
return true; //
} // await _player!.pausePlayer();
// // emit(state.copyWith(playState: 2));
/// 清空播放 // return true;
Future<bool> clearAudio() async { // }
try { //
await _player?.closePlayer(); // /// 恢复播放
} catch (e) { // Future<bool> resumeAudio() async {
print(e); // if (!state.playerIsInit) {
} // throw Exception("播放器未初始化");
emit(state.copyWith(playerIsInit: false, playState: 0, playId: '')); // }
return true; //
} // // if (state.playState != 2) {
// // throw Exception("播放器状态错误");
// // }
//
// if (!_player!.isPaused) {
// 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 && state.playState != 2) {
// // throw Exception("播放器状态错误");
// // }
//
// if (!_player!.isPlaying && !_player!.isPaused) {
// throw Exception("播放器状态错误");
// }
//
// await _player!.stopPlayer();
// // emit(state.copyWith(playState: 0));
// return true;
// }
//
// /// 清空播放
// Future<bool> clearAudio() async {
// try {
// await _player?.closePlayer();
// _player = null;
// await _playerSubscription?.cancel();
// _playerSubscription = null;
// _playDuration = null;
// } catch (e) {
// print(e);
// }
// // emit(state.copyWith(playerIsInit: false, playState: 0, playId: ''));
// emit(state.copyWith(playerIsInit: false, playId: ''));
// return true;
// }
@override @override
Future<void> close() async { Future<void> close() async {
...@@ -870,17 +1067,25 @@ class WebCubit extends Cubit<WebState> { ...@@ -870,17 +1067,25 @@ class WebCubit extends Cubit<WebState> {
// closeLocalRecorder(); // closeLocalRecorder();
// closeLocalPlayer(); // closeLocalPlayer();
try { await _playerService.close();
await _recorder?.closeRecorder(); await _recorderService.close();
} catch (e) {
print(e);
}
try { // try {
await _player?.closePlayer(); // await _recorder?.closeRecorder();
} catch (e) { // _recorder = null;
print(e); // } catch (e) {
} // print(e);
// }
// try {
// await _player?.closePlayer();
// _player = null;
// await _playerSubscription?.cancel();
// _playerSubscription = null;
// _playDuration = null;
// } catch (e) {
// print(e);
// }
return super.close(); return super.close();
} }
......
class Constant { class Constant {
/// 应用内部 http 服务 /// 应用内部 http 服务
static const int localServerPort = 35982; static const int localServerPort = 35982;
static const String localServerHost = '127.0.0.1'; static const String localServerHost = '127.0.0.1';
// static const String localServerHost = 'appdev-xj.banxiaoer.net';
static const String localServerUrl = 'http://$localServerHost:$localServerPort'; static const String localServerUrl = 'http://$localServerHost:$localServerPort';
static const String localFileUrl = 'http://127.0.0.1:$localServerPort';
static const String localServerTemp = '/temp'; static const String localServerTemp = '/temp';
static const String localServerTempFileUrl = '$localServerUrl$localServerTemp'; static const String localServerTempFileUrl = '$localFileUrl$localServerTemp';
static const String localServerTest = '/test'; static const String localServerTest = '/test';
static const String localServerTestFileUrl = '$localServerUrl$localServerTest'; static const String localServerTestFileUrl = '$localFileUrl$localServerTest';
/// obs文件分片上传的分片大小:5M /// obs文件分片上传的分片大小:5M
static const int obsUploadChunkSize = 1024 * 1024 * 5; static const int obsUploadChunkSize = 1024 * 1024 * 5;
/// 版本号
static const String appVersion = '1.0.0';
static const String h5Version = '1.0.0';
/// IM SDK
static const int imSdkAppId = 1400310691;
static const String imClientSecure = 'kM4yqbehB3io9UiLvH6eHvM7xAhfYxoyyaO1tLoHgKltcaI7MZXkUbpFaWdeQIqe';
/// 测试阶段使用的 h5 服务地址 /// 测试阶段使用的 h5 服务地址
// static const String h5Server = 'appdev-xj.banxiaoer.net'; static const String h5Server = 'appdev-xj.banxiaoer.net';
static const String h5Server = 'appdev-th.banxiaoer.net';
// static const String h5Server = 'appdev-th.banxiaoer.net';
// static const String h5Server = '192.168.1.136'; // static const String h5Server = '192.168.1.136';
/// 测试阶段使用
static const bool needIM = true;
} }
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
late final Database bxeDb;
Future<void> initDatabase() async {
// 获取数据库存储的默认路径
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'bxe_database.db');
// 打开数据库,如果不存在则创建
Database database = await openDatabase(
path,
version: 1, // 数据库版本
onCreate: (Database db, int version) async {
// 创建表
await db.execute(
// 'CREATE TABLE msg(id INTEGER PRIMARY KEY, content TEXT)',
'CREATE TABLE msg(id INTEGER, content TEXT)',
);
},
);
bxeDb = database;
}
import 'dart:io' show Platform;
import 'package:appframe/data/repositories/message/app_info_handler.dart'; 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_player_handler.dart';
import 'package:appframe/data/repositories/message/audio_recorder_handler.dart'; import 'package:appframe/data/repositories/message/audio_recorder_handler.dart';
...@@ -20,10 +22,9 @@ import 'package:appframe/data/repositories/message/orientation_handler.dart'; ...@@ -20,10 +22,9 @@ 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_file_to_disk_handler.dart';
import 'package:appframe/data/repositories/message/save_to_album_handler.dart'; import 'package:appframe/data/repositories/message/save_to_album_handler.dart';
import 'package:appframe/data/repositories/message/scan_code_handler.dart'; import 'package:appframe/data/repositories/message/scan_code_handler.dart';
import 'package:appframe/data/repositories/message/set_title_handler.dart';
import 'package:appframe/data/repositories/message/storage_handler.dart'; import 'package:appframe/data/repositories/message/storage_handler.dart';
import 'package:appframe/data/repositories/message/title_bar_handler.dart';
import 'package:appframe/data/repositories/message/upload_file.dart'; import 'package:appframe/data/repositories/message/upload_file.dart';
import 'package:appframe/data/repositories/message/upload_file2.dart';
import 'package:appframe/data/repositories/message/vibrate_short_handler.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/video_info_handler.dart';
import 'package:appframe/data/repositories/message/wifi_info_handler.dart'; import 'package:appframe/data/repositories/message/wifi_info_handler.dart';
...@@ -31,10 +32,13 @@ import 'package:appframe/data/repositories/message/window_info_handler.dart'; ...@@ -31,10 +32,13 @@ import 'package:appframe/data/repositories/message/window_info_handler.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart'; import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart'; import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/im_service.dart';
import 'package:appframe/services/local_server_service.dart';
import 'package:appframe/services/player_service.dart';
import 'package:appframe/services/recorder_service.dart';
import 'package:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'dart:io' show Platform;
final getIt = GetIt.instance; final getIt = GetIt.instance;
...@@ -44,7 +48,10 @@ Future<void> setupLocator() async { ...@@ -44,7 +48,10 @@ Future<void> setupLocator() async {
await (() async { await (() async {
Fluwx fluwx = Fluwx(); Fluwx fluwx = Fluwx();
if (Platform.isAndroid || Platform.isIOS) { if (Platform.isAndroid || Platform.isIOS) {
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://dev.banxiaoer.net/path/to/wechat/"); await fluwx.registerApi(
appId: "wx8c32ea248f0c7765",
universalLink: "https://dev.banxiaoer.net/path/to/wechat/",
);
} }
return fluwx; return fluwx;
})(), })(),
...@@ -156,14 +163,14 @@ Future<void> setupLocator() async { ...@@ -156,14 +163,14 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<MessageHandler>(() => ScanCodeHandler(), instanceName: 'scanCode'); getIt.registerLazySingleton<MessageHandler>(() => ScanCodeHandler(), instanceName: 'scanCode');
/// 上传文件 /// 上传文件
// getIt.registerLazySingleton<MessageHandler>(() => UploadFileHandler(), instanceName: 'uploadFile'); getIt.registerLazySingleton<MessageHandler>(() => UploadFileHandler(), instanceName: 'uploadFile');
getIt.registerLazySingleton<MessageHandler>(() => UploadFile2Handler(), instanceName: 'uploadFile');
/// 下载文件 /// 下载文件
getIt.registerLazySingleton<MessageHandler>(() => DownloadFileHandler(), instanceName: 'downloadFile'); getIt.registerLazySingleton<MessageHandler>(() => DownloadFileHandler(), instanceName: 'downloadFile');
/// 设置标题和返回按钮 /// 设置标题和返回按钮
getIt.registerLazySingleton<MessageHandler>(() => SetTitleHandler(), instanceName: 'setTitle'); // getIt.registerLazySingleton<MessageHandler>(() => SetTitleHandler(), instanceName: 'setTitle');
getIt.registerLazySingleton<MessageHandler>(() => TitleBarHandler(), instanceName: 'setTitlebar');
/// 新路由打开链接 /// 新路由打开链接
getIt.registerLazySingleton<MessageHandler>(() => OpenLinkHandler(), instanceName: 'openlink'); getIt.registerLazySingleton<MessageHandler>(() => OpenLinkHandler(), instanceName: 'openlink');
...@@ -172,8 +179,28 @@ Future<void> setupLocator() async { ...@@ -172,8 +179,28 @@ Future<void> setupLocator() async {
getIt.registerLazySingleton<MessageHandler>(() => GoLoginHandler(), instanceName: 'goLogin'); getIt.registerLazySingleton<MessageHandler>(() => GoLoginHandler(), instanceName: 'goLogin');
/// service /// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net')); ///
/// local server
getIt.registerSingleton<LocalServerService>(LocalServerService());
/// apiService
getIt.registerLazySingleton<ApiService>(
() => ApiService(baseUrl: 'https://dev.banxiaoer.net'),
instanceName: "bxeApiService",
);
getIt.registerLazySingleton<ApiService>(
() => ApiService(baseUrl: 'https://iotapp-dev.banxiaoer.com/iotapp'),
instanceName: "appApiService",
);
/// imService
getIt.registerSingleton<ImService>(ImService());
/// 播放
getIt.registerSingleton<PlayerService>(PlayerService());
/// 录音
getIt.registerSingleton<RecorderService>(RecorderService());
/// repository /// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository()); getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
......
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/services/mqtt_service.dart';
/// 此处暂时测试 /// 此处暂时测试
/// 正常需要在登录状态下,查询host和jwt /// 正常需要在登录状态下,查询host和jwt
Future<void> registerMqtt() async { Future<void> registerMqtt() async {
String mqttHost = '58.87.99.45'; // String mqttHost = '58.87.99.45';
int mqttPort = 1883; // int mqttPort = 1883;
String mqttClientId = 'asdfasdf'; // // 获取 mac 地址
int keepAlive = 60; // String mqttMac = '';
String mqttUsername = 'user'; // String mqttClientId = 'tanghuan_phone';
String mqttPassword = // int keepAlive = 60;
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjIzOTY5MDQsInJzIjoib2sifQ.tsouW-uHTe6ndiEqSE4wcR_oSScuoS0qVvqWtNSaXgWkX2yEja9ImnMYr6jNKxJY8_qox9UdXRuCept5cTM4-8ZaA0BBlYXwr3LbC3yPmniFhs-tAMFbZqG2-3r0sc5NMbE3M1fXMi3dmvQc2AlyazheL98EmRNILFsz6pZ-x5rR1gFulZ53PXa7fX60XRlXg8jc3-89gdUJcS0MOMU7yyU0Sv3QBBplLr3CmWCoYX99a_3QHvq3o7aUTJ_Ed5Ms9_3k4QHgawsfdTWjZkyBouAvLMgVkt4D0PJbhgsjzAaA01Q7jEJ_SokNojOFfQYuHNScczmlOLXocJCCD4189A'; // String mqttUsername = 'user';
// String mqttPassword =
// 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjI2NTgzOTEsInJzIjoib2sifQ.AoU0MfSKflC1VB0abi7BVr3g4MDah1uLlg01ZTFQgTxfolu28IfZ4BaGhRF9qy7yAQH2Efmdf2cs2iwKrdcHRSHzJhwTC44beX6viRhCCiCxe51AB8NVv72l2TmsNxIvACfXOhDLjKH6QE38EaKC486aS_L-QpakvDOQP_IPjq5ZvH68JwwhOwhLTgaCgOR3xde2H-NgRDK2BQ-FyDTXi1RX8hDGvKMw8pi6WiVBjR1ENTO5A7yvMioJS9qwdjs_7_5c6n5GXSjCHTtdQ7746hlId2uwP_41G5Ug3DYWiZ5aWIuvGRH6ZxKmbC32wN62ys_XkLGzhBw8wsQ-KhETvQ ';
//
// /// 初始化MQTT客户端
// var mqttService = MqttService(mqttHost, mqttPort, mqttClientId, keepAlive, mqttUsername, mqttPassword);
// await mqttService.initConn();
//
// /// 设置到getIt,用于获取使用
// getIt.registerSingleton(mqttService);
/// 初始化MQTT客户端 // MqttIsolateManager mqttIsolateManager = MqttIsolateManager();
var mqttService = MqttService(mqttHost, mqttPort, mqttClientId, keepAlive, mqttUsername, mqttPassword); // mqttIsolateManager.start();
await mqttService.initConn(); // await mqttIsolateManager.connect('server', 'clientId');
// // 暂停3秒
/// 设置到getIt,用于获取使用 // // await Future.delayed(Duration(seconds: 2));
getIt.registerSingleton(mqttService); // mqttIsolateManager.subscribe('bxe/abc');
//
// getIt.registerSingleton(mqttIsolateManager);
} }
import 'package:appframe/ui/pages/adv_page.dart'; import 'package:appframe/ui/pages/adv_page.dart';
import 'package:appframe/ui/pages/im_page.dart';
import 'package:appframe/ui/pages/link_page.dart'; import 'package:appframe/ui/pages/link_page.dart';
import 'package:appframe/ui/pages/login_main_page.dart'; import 'package:appframe/ui/pages/login_main_page.dart';
import 'package:appframe/ui/pages/login_phone_page.dart'; import 'package:appframe/ui/pages/login_phone_page.dart';
import 'package:appframe/ui/pages/mqtt_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart'; import 'package:appframe/ui/pages/scan_code_page.dart';
import 'package:appframe/ui/pages/web_page.dart'; import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/pages/wechat_auth_page.dart'; import 'package:appframe/ui/pages/wechat_auth_page.dart';
...@@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; ...@@ -10,7 +10,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
final GoRouter router = GoRouter( final GoRouter router = GoRouter(
initialLocation: '/adv', initialLocation: '/web',
routes: <RouteBase>[ routes: <RouteBase>[
GoRoute( GoRoute(
path: '/web', path: '/web',
...@@ -55,9 +55,9 @@ final GoRouter router = GoRouter( ...@@ -55,9 +55,9 @@ final GoRouter router = GoRouter(
}, },
), ),
GoRoute( GoRoute(
path: '/mqtt', path: '/im',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
return const MqttPage(); return const ImPage();
}, },
), ),
], ],
......
import 'package:appframe/bloc/web_cubit.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/player_service.dart';
import 'package:uuid/uuid.dart';
class AudioPlayHandler extends MessageHandler { class AudioPlayHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
try {
if (params is! Map<String, dynamic>) { if (params is! Map<String, dynamic>) {
throw Exception('参数错误'); throw Exception('参数错误');
} }
final url = params['url'] as String; final url = params['url'] as String;
final seek = params['seek'] as int? ?? 0;
bool result = await _webCubit!.playAudio(url); // 暂时忽略
return result; // final isBg = params['isBg'] as bool ?? false;
} finally { var playId = params['playId'] as String? ?? '';
_unfollowCubit(); if (playId.isEmpty) {
} playId = Uuid().v4();
} }
}
class AudioPauseHandler extends MessageHandler { var playerService = getIt.get<PlayerService>();
late WebCubit? _webCubit; var result = await playerService.playAudio(url, seek, playId);
@override if (!result) {
void setCubit(WebCubit cubit) { throw Exception('播放错误');
this._webCubit = cubit;
} }
return {'playId': playId};
void _unfollowCubit() {
this._webCubit = null;
} }
}
class AudioPauseHandler extends MessageHandler {
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.pauseAudio(); return await getIt.get<PlayerService>().pauseAudio();
return result;
} }
} }
class AudioResumeHandler extends MessageHandler { class AudioResumeHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.resumeAudio(); return await getIt.get<PlayerService>().resumeAudio();
return result;
} }
} }
class AudioSeekHandler extends MessageHandler { class AudioSeekHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) { if (params is! Map<String, dynamic>) {
...@@ -87,45 +51,21 @@ class AudioSeekHandler extends MessageHandler { ...@@ -87,45 +51,21 @@ class AudioSeekHandler extends MessageHandler {
} }
final seek = params['seek'] as int; final seek = params['seek'] as int;
bool result = await _webCubit!.seekAudio(seek); return await getIt.get<PlayerService>().seekAudio(seek);
return result;
} }
} }
class AudioStopHandler extends MessageHandler { class AudioStopHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
bool result = await _webCubit!.stopAudio(); return await getIt.get<PlayerService>().stopAudio();
return result;
} }
} }
class AudioClearHandler extends MessageHandler { class AudioClearHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
await _webCubit!.clearAudio(); await getIt.get<PlayerService>().clearAudio();
return true; return true;
} }
} }
import 'package:appframe/bloc/web_cubit.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/services/recorder_service.dart';
class AudioRecorderStartHandler extends MessageHandler { class AudioRecorderStartHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
try { // 0代表不限制时长
bool result = await _webCubit!.startRecording(); final duration = params['duration'] as int? ?? 0;
return result;
} finally { return await getIt.get<RecorderService>().startRecording(duration);
_unfollowCubit();
}
} }
} }
class AudioRecorderPauseHandler extends MessageHandler { class AudioRecorderPauseHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
try { return await getIt.get<RecorderService>().pauseRecording();
bool result = await _webCubit!.pauseRecording();
return result;
} finally {
_unfollowCubit();
}
} }
} }
class AudioRecorderResumeHandler extends MessageHandler { class AudioRecorderResumeHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
try { return await getIt.get<RecorderService>().resumeRecording();
bool result = await _webCubit!.resumeRecording();
return result;
} finally {
_unfollowCubit();
}
} }
} }
class AudioRecorderStopHandler extends MessageHandler { class AudioRecorderStopHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
try { return await getIt.get<RecorderService>().stopRecording();
return await _webCubit!.stopRecording();
} finally {
_unfollowCubit();
}
} }
} }
class AudioRecorderClearHandler extends MessageHandler { class AudioRecorderClearHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override @override
Future<dynamic> handleMessage(dynamic params) async { Future<dynamic> handleMessage(dynamic params) async {
try { return await getIt.get<RecorderService>().clearRecording();
return await _webCubit!.clearRecording();
} finally {
_unfollowCubit();
}
} }
} }
import 'dart:io'; import 'dart:io';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/video_util.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:video_compress/video_compress.dart'; import 'package:uuid/uuid.dart';
///
/// 压缩图片 /// 压缩图片
/// ///
class CompressImageHandler extends MessageHandler { class CompressImageHandler extends MessageHandler {
...@@ -84,6 +86,7 @@ class CompressImageHandler extends MessageHandler { ...@@ -84,6 +86,7 @@ class CompressImageHandler extends MessageHandler {
} }
} }
///
/// 压缩视频 /// 压缩视频
/// ///
class CompressVideoHandler extends MessageHandler { class CompressVideoHandler extends MessageHandler {
...@@ -105,11 +108,11 @@ class CompressVideoHandler extends MessageHandler { ...@@ -105,11 +108,11 @@ class CompressVideoHandler extends MessageHandler {
throw Exception('参数错误'); throw Exception('参数错误');
} }
Directory tempDir = await getTemporaryDirectory();
String srcPath; String srcPath;
if (url.startsWith('http')) { if (url.startsWith('http')) {
String ext = path.extension(url); String ext = path.extension(url);
final Directory tempDir = await getTemporaryDirectory(); srcPath = '${tempDir.path}/${Uuid().v4()}$ext';
srcPath = path.join(tempDir.path, '${DateTime.now().millisecondsSinceEpoch}$ext');
// Dio 下载文件 // Dio 下载文件
final resp = await Dio().download(url, srcPath); final resp = await Dio().download(url, srcPath);
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
...@@ -119,28 +122,49 @@ class CompressVideoHandler extends MessageHandler { ...@@ -119,28 +122,49 @@ class CompressVideoHandler extends MessageHandler {
srcPath = url; srcPath = url;
} }
VideoQuality videoQuality; var originFile = File(srcPath);
switch (quality) { if (!originFile.existsSync()) {
case 'low': throw Exception('文件不存在');
videoQuality = VideoQuality.LowQuality;
break;
case 'middle':
videoQuality = VideoQuality.MediumQuality;
break;
case 'high':
videoQuality = VideoQuality.HighestQuality;
break;
default:
throw Exception('参数错误');
} }
print('原视频大小:${originFile.lengthSync()}');
final mediaInfo = await VideoCompress.compressVideo(
srcPath, final outputPath = '${tempDir.path}/${Uuid().v4()}.mp4';
quality: videoQuality, var result = await VideoUtil.compressVideo(srcPath, outputPath, quality);
deleteOrigin: false, if (!result) {
includeAudio: true, throw Exception('视频压缩失败');
); }
print('压缩后视频大小:${File(outputPath).lengthSync()}');
return {"tempFilePath": "/temp${mediaInfo!.path}", "size": mediaInfo.filesize};
return {
"tempFilePath": '/temp$outputPath',
"size": File(outputPath).lengthSync(),
};
// 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,
// };
} }
} }
...@@ -37,8 +37,8 @@ class CropImageHandler extends MessageHandler { ...@@ -37,8 +37,8 @@ class CropImageHandler extends MessageHandler {
} }
Future<String> _cropImageByRatio(String url, String cropScale) async { Future<String> _cropImageByRatio(String url, String cropScale) async {
if (url.startsWith(Constant.localServerUrl)) { if (url.startsWith(Constant.localFileUrl)) {
url = url.replaceFirst(Constant.localServerUrl, ''); url = url.replaceFirst(Constant.localFileUrl, '');
} }
if (url.startsWith(Constant.localServerTemp)) { if (url.startsWith(Constant.localServerTemp)) {
......
...@@ -15,8 +15,8 @@ class OpenDocumentHandler extends MessageHandler { ...@@ -15,8 +15,8 @@ class OpenDocumentHandler extends MessageHandler {
throw Exception('参数错误'); throw Exception('参数错误');
} }
if (url.startsWith(Constant.localServerUrl)) { if (url.startsWith(Constant.localFileUrl)) {
url = url.replaceFirst(Constant.localServerUrl, ''); url = url.replaceFirst(Constant.localFileUrl, '');
} }
if (url.startsWith(Constant.localServerTemp)) { if (url.startsWith(Constant.localServerTemp)) {
......
...@@ -28,7 +28,11 @@ class OpenWeappHandler extends MessageHandler { ...@@ -28,7 +28,11 @@ class OpenWeappHandler extends MessageHandler {
try { try {
return await _fluwx.open( return await _fluwx.open(
target: MiniProgram(username: appid, path: path, miniProgramType: _getWXMiniProgramType(envVersion)), target: MiniProgram(
username: appid,
path: path,
miniProgramType: _getWXMiniProgramType(envVersion),
),
); );
} catch (e) { } catch (e) {
print(e); print(e);
......
import 'package:appframe/bloc/web_cubit.dart'; // import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart'; // import 'package:appframe/services/dispatcher.dart';
//
class SetTitleHandler extends MessageHandler { // class SetTitleHandler extends MessageHandler {
late WebCubit? _webCubit; // late WebCubit? _webCubit;
//
@override // @override
void setCubit(WebCubit cubit) { // void setCubit(WebCubit cubit) {
this._webCubit = cubit; // this._webCubit = cubit;
} // }
//
void _unfollowCubit() { // void _unfollowCubit() {
this._webCubit = null; // this._webCubit = null;
} // }
//
@override // @override
Future<dynamic> handleMessage(params) async { // Future<dynamic> handleMessage(params) async {
try { // try {
if (params is! Map<String, dynamic>) { // if (params is! Map<String, dynamic>) {
throw Exception('参数错误'); // throw Exception('参数错误');
} // }
//
final String title = params['title'] as String; // final String title = params['title'] as String;
final bool showBack = params['showBack'] as bool; // final bool showBack = params['showBack'] as bool;
//
return _webCubit!.setTitle(title, showBack); // return _webCubit!.setTitle(title, showBack);
} finally { // } finally {
_unfollowCubit(); // _unfollowCubit();
} // }
} // }
} // }
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
class TitleBarHandler extends MessageHandler {
late WebCubit? _webCubit;
@override
void setCubit(WebCubit cubit) {
this._webCubit = cubit;
}
void _unfollowCubit() {
this._webCubit = null;
}
@override
Future<dynamic> handleMessage(params) async {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final String title = params['title'] as String;
final String color = params['color'] as String;
final String bgColor = params['bgColor'] as String;
final String icon = params['icon'] as String;
return _webCubit!.setTitleBar(title, color, bgColor, icon);
} finally {
_unfollowCubit();
}
}
}
...@@ -4,17 +4,34 @@ import 'package:appframe/config/constant.dart'; ...@@ -4,17 +4,34 @@ import 'package:appframe/config/constant.dart';
import 'package:appframe/services/api_service.dart'; import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart'; import 'package:appframe/utils/file_type_util.dart';
import 'package:appframe/utils/video_util.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logger/logger.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class UploadFileHandler extends MessageHandler { class UploadFileHandler extends MessageHandler {
static final upLogger = Logger(level: Level.debug); // late Dio _dio;
// UploadFile5Handler() : _dio = Dio() {
// // _dio.httpClientAdapter = Http2Adapter(
// // ConnectionManager(idleTimeout: Duration(seconds: 10)),
// // );
//
// int connectTimeout = 30000;
// int receiveTimeout = 30000;
//
// _dio.options = BaseOptions(
// baseUrl: '',
// connectTimeout: Duration(milliseconds: connectTimeout),
// receiveTimeout: Duration(milliseconds: receiveTimeout),
// headers: {'Content-Type': '', 'Accept': ''},
// );
// }
@override @override
Future handleMessage(params) async { Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) { if (params is! Map<String, dynamic>) {
throw Exception('参数错误'); throw Exception('参数错误');
} }
...@@ -33,174 +50,190 @@ class UploadFileHandler extends MessageHandler { ...@@ -33,174 +50,190 @@ class UploadFileHandler extends MessageHandler {
throw Exception('参数错误'); throw Exception('参数错误');
} }
final startTimestamp = DateTime.now().millisecondsSinceEpoch; // _dio = Dio()
upLogger.d('开始上传文件'); // ..options = BaseOptions(
// baseUrl: '',
final result = await compute(_handleUpload, {'filePath': tempFilePath, 'busi': busi, 'subBusi': subBusi}); // connectTimeout: Duration(milliseconds: 30000),
// final result = await _handleUpload({'filePath': tempFilePath}); // receiveTimeout: Duration(milliseconds: 30000),
// headers: {'Content-Type': '', 'Accept': ''},
final endTimestamp = DateTime.now().millisecondsSinceEpoch; // )
upLogger.d('上传完成,耗时:${endTimestamp - startTimestamp} 毫秒'); // /*..httpClientAdapter = Http2Adapter(
// ConnectionManager(idleTimeout: Duration(seconds: 10)),
result['startTimestamp'] = startTimestamp; // )*/
result['sendTimestamp'] = endTimestamp; // ;
final startTime = DateTime.now();
final result = await _handle(tempFilePath, busi, subBusi);
final endTime = DateTime.now();
print('====================>上传耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
// result['startTime'] = startTime.toString();
// result['endTime'] = endTime.toString();
return result; return result;
} }
static const _bxeBaseUrl = 'https://iotapp-dev.banxiaoer.com/iotapp'; Future<Map<String, dynamic>> _handle(String filePath, String busi, String subBusi) async {
static const _signatureNewUrl = '/api/v1/obs/multipart/signaturenew'; ///
static const _signatureNextUrl = '/api/v1/obs/multipart/signaturenext'; /// 1 判断
static const _completeUrl = '/api/v1/obs/multipart/complete'; ///
if (filePath.startsWith(Constant.localFileUrl)) {
/// 在Isolate中执行 filePath = filePath.replaceFirst(Constant.localFileUrl, '');
static Future<Map<String, dynamic>> _handleUpload(Map<String, dynamic> fileParams) async {
String filePath = fileParams['filePath'] as String;
String busi = fileParams['busi'] as String;
String subBusi = fileParams['subBusi'] as String;
if (filePath.startsWith(Constant.localServerUrl)) {
filePath = filePath.replaceFirst(Constant.localServerUrl, '');
} }
if (filePath.startsWith(Constant.localServerTemp)) { if (filePath.startsWith(Constant.localServerTemp)) {
filePath = filePath.replaceFirst(Constant.localServerTemp, ''); filePath = filePath.replaceFirst(Constant.localServerTemp, '');
} }
final bxeApiService = ApiService(baseUrl: _bxeBaseUrl);
// 由于服务端签名时未设置Content-Type,这里必须设置为空,否则会报签名错误
// 由于封装有默认值,所以不能不设置
final obsApiService = ApiService(defaultHeaders: {'Content-Type': '', 'Accept': ''});
String logicPrefix = _getLoginPrefix(busi, subBusi);
//并行上传分段
upLogger.d("开始处理并行上传");
final uploadResult =
await _uploadInParallel(bxeApiService, obsApiService, logicPrefix, filePath, maxConcurrency: 30);
upLogger.d("并行上传完成");
// 上传结果
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>;
//请求合并文件
upLogger.d("开始处理合并文件");
String location = await _merge(bxeApiService, objectKey, bucket, uploadId, tagsMap);
upLogger.d("合并文件完成");
//关闭Dio
bxeApiService.close();
obsApiService.close();
return {'url': _addPreUrl(location)};
}
/// 并行上传
static Future<Map<String, dynamic>> _uploadInParallel(
ApiService bxeApiService,
ApiService obsApiService,
String logicPrefix,
String filePath, {
int maxConcurrency = 5,
}) async {
//判断文件 //判断文件
File file = File(filePath); File file = File(filePath);
if (!file.existsSync()) { if (!file.existsSync()) {
throw Exception('文件不存在'); throw Exception('文件不存在');
} }
//暂时仅支持200M的文件上传 //暂时仅支持200M的文件上传
final fileSize = file.lengthSync(); var fileSize = file.lengthSync();
if (fileSize > 1024 * 1024 * 200) { if (fileSize > 1024 * 1024 * 200) {
throw Exception('上传的文件过大'); throw Exception('上传的文件过大');
} }
print('原始文件大小:$fileSize 字节');
///
/// 视频文件上传之前进行压缩
/// 非 mp4 格式的视频文件需先转码
///
String? mimeType = await FileTypeUtil.getMimeType(file);
if (mimeType?.startsWith('video/') ?? false) {
final inputPath = filePath;
final tempDir = await getTemporaryDirectory();
final outputPath = '${tempDir.path}/${Uuid().v4()}.mp4';
var startTime = DateTime.now();
if (mimeType != 'video/mp4') {
await VideoUtil.convertToMp4(inputPath, outputPath);
} else {
await VideoUtil.compressVideo(inputPath, outputPath, 'low');
}
var endTime = DateTime.now();
print('====================>压缩耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
upLogger.d('文件大小:$fileSize'); file = File(outputPath);
fileSize = file.lengthSync();
//分段大小2M }
final chunkSize = Constant.obsUploadChunkSize;
upLogger.d('分段大小:$chunkSize');
//分段总数
final totalChunks = (fileSize / chunkSize).ceil();
upLogger.d('分段总数:$totalChunks');
final randomAccessFile = file.openSync();
//bucket 存储桶名称 : bxe-files | bxe-pics | bxe-videos /// 2
/// bucket 存储桶名称 : bxe-files | bxe-pics | bxe-videos
///
String bucket; String bucket;
if (await FileTypeUtil.isImage(file)) { if (mimeType?.startsWith('image/') ?? false) {
bucket = 'bxe-pics'; bucket = 'bxe-pics';
} else if (await FileTypeUtil.isVideo(file)) { } else if (mimeType?.startsWith('video/') ?? false) {
bucket = 'bxe-videos'; bucket = 'bxe-videos';
} else { } else {
bucket = 'bxe-files'; bucket = 'bxe-files';
} }
//生成唯一文件名, /// 3
// String objectKey = 'd2/test/file.csv'; /// objectKey
var uuid = Uuid(); var uuid = Uuid();
String logicPrefix = _getLoginPrefix(busi, subBusi);
String objectKey = '$logicPrefix/${uuid.v4()}${path.extension(file.path)}'; String objectKey = '$logicPrefix/${uuid.v4()}${path.extension(file.path)}';
String uploadId = '';
Map<int, String> tagsMap = {};
final futures = <Future>[]; ///
/// 4 计算分片
///
final chunkSize = Constant.obsUploadChunkSize;
final totalChunks = (fileSize / chunkSize).ceil();
print('上传文件大小:$fileSize 字节');
print('分片数量:$totalChunks');
///
/// 5 sig
///
var startTime1 = DateTime.now();
print('====================>签名开始 $startTime1');
final bxeApiService = ApiService(baseUrl: _bxeBaseUrl);
late String uploadId;
var signUrls = [];
for (int i = 0; i < totalChunks; i++) { for (int i = 0; i < totalChunks; i++) {
upLogger.d('开始处理分段:$i'); if (i == 0) {
final initResult = await _init(bxeApiService, objectKey, bucket);
// 控制并发数量 uploadId = initResult['upload_id'] as String;
if (futures.length >= maxConcurrency) { var signUrl = initResult['signed_url'] as String;
upLogger.d('超过最大并发数量,等待'); signUrls.add(signUrl);
await Future.wait(futures); } else {
futures.clear(); final nextResult = await _next(bxeApiService, objectKey, bucket, uploadId, i + 1);
var signUrl = nextResult['signed_url'] as String;
signUrls.add(signUrl);
}
} }
var endTime1 = DateTime.now();
print('====================>签名耗时:${endTime1.millisecondsSinceEpoch - startTime1.millisecondsSinceEpoch} 毫秒');
///
/// 6 上传
///
final dio = Dio()
..options = BaseOptions(
baseUrl: '',
connectTimeout: Duration(milliseconds: 30000),
receiveTimeout: Duration(milliseconds: 30000),
headers: {'Content-Type': '', 'Accept': ''},
);
final randomAccessFile = await file.open();
Map<int, String> tagsMap = {};
final futures = <Future>[];
for (int i = 0; i < totalChunks; i++) {
final chunkSize = Constant.obsUploadChunkSize;
final start = i * chunkSize; final start = i * chunkSize;
final actualChunkSize = (i + 1) * chunkSize > fileSize ? fileSize - start : chunkSize; final actualChunkSize = (i + 1) * chunkSize > fileSize ? fileSize - start : chunkSize;
final chunk = Uint8List(actualChunkSize);
final chunk = Uint8List(actualChunkSize);
randomAccessFile.setPositionSync(start); randomAccessFile.setPositionSync(start);
await randomAccessFile.readInto(chunk, 0, actualChunkSize); await randomAccessFile.readInto(chunk, 0, actualChunkSize);
final startTime = DateTime.now().millisecondsSinceEpoch; futures.add(_uploadChunkWithRetry(dio, signUrls[i], i, chunk));
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;
} }
final endTime = DateTime.now().millisecondsSinceEpoch;
upLogger.d('分段$i,签名耗时:${endTime - startTime} 毫秒');
// await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap); var resultList = await Future.wait(futures);
final future = _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap); for (var result in resultList) {
futures.add(future); if (result is Map<String, dynamic>) {
tagsMap[result['idx'] as int] = result['etag'] as String;
} }
// 等待剩余的上传完成
if (futures.isNotEmpty) {
await Future.wait(futures);
futures.clear();
} }
futures.clear();
randomAccessFile.closeSync(); await randomAccessFile.close();
return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap}; dio.close(force: true);
///
/// 7 合并
///
var startTime2 = DateTime.now();
String location = await _merge(bxeApiService, objectKey, bucket, uploadId, tagsMap);
var endTime2 = DateTime.now();
print('====================>合并签名耗时:${endTime2.millisecondsSinceEpoch - startTime2.millisecondsSinceEpoch} 毫秒');
bxeApiService.close();
return {'url': _addPreUrl(location)};
} }
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';
/// 初始化,请求后端获取签名信息和上传任务ID /// 初始化,请求后端获取签名信息和上传任务ID
static Future<Map<String, dynamic>> _init(ApiService bxeApiService, String objectKey, String bucket) async { Future<Map<String, dynamic>> _init(ApiService bxeApiService, String objectKey, String bucket) async {
var endpoint = '$_signatureNewUrl?objectKey=$objectKey&bucket=$bucket'; var endpoint = '$_signatureNewUrl?objectKey=$objectKey&bucket=$bucket';
final resp = await bxeApiService.get(endpoint); final resp = await bxeApiService.get(endpoint);
return resp.data; return resp.data;
} }
/// 每次上传前,请求后端获取签名信息 /// 每次上传前,请求后端获取签名信息
static Future<Map<String, dynamic>> _next( Future<Map<String, dynamic>> _next(
ApiService bxeApiService, ApiService bxeApiService,
String objectKey, String objectKey,
String bucket, String bucket,
...@@ -213,30 +246,29 @@ class UploadFileHandler extends MessageHandler { ...@@ -213,30 +246,29 @@ class UploadFileHandler extends MessageHandler {
} }
/// 上传段,按照最大重试次数进行上传重试 /// 上传段,按照最大重试次数进行上传重试
static Future<void> _uploadChunkWithRetry( Future<Map<String, dynamic>> _uploadChunkWithRetry(
ApiService obsApiService, Dio dio,
String signUrl, String signUrl,
int chunkIndex, int chunkIndex,
Uint8List chunk, Uint8List chunk, {
Map<int, String> tagsMap, {
int maxRetries = 3, int maxRetries = 3,
}) async { }) async {
final start = DateTime.now().millisecondsSinceEpoch; //print('====================> 分片$chunkIndex , 开始上传 ${DateTime.now()}');
upLogger.d('分段$chunkIndex,开始时间:$start毫秒');
for (int attempt = 0; attempt <= maxRetries; attempt++) { for (int attempt = 0; attempt <= maxRetries; attempt++) {
try { try {
final resp = await _uploadChunk(obsApiService, signUrl, chunk, chunkIndex); var starTime = DateTime.now();
final resp = await _uploadChunk(dio, signUrl, chunk, chunkIndex);
var endTime = DateTime.now();
if (resp.statusCode == 200) { if (resp.statusCode == 200) {
print(
'====================> 分片$chunkIndex${attempt + 1}次, $endTime 上传耗时:${endTime.millisecondsSinceEpoch - starTime.millisecondsSinceEpoch} 毫秒');
final etags = resp.headers['etag'] as List<String>; final etags = resp.headers['etag'] as List<String>;
tagsMap[chunkIndex + 1] = etags[0]; return Future.value({'idx': chunkIndex + 1, 'etag': etags[0]}); // 上传成功
final end = DateTime.now().millisecondsSinceEpoch;
upLogger.d('分段$chunkIndex,上传段耗时:${end - start}毫秒');
return; // 上传成功
} else { } else {
throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}'); throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}');
} }
} catch (e) { } catch (e) {
print('====================> 分片$chunkIndex${attempt + 1}次, 上传失败:${e.toString()}');
if (attempt == maxRetries) { if (attempt == maxRetries) {
throw Exception('Chunk $chunkIndex upload failed after $maxRetries attempts: $e'); throw Exception('Chunk $chunkIndex upload failed after $maxRetries attempts: $e');
} }
...@@ -244,19 +276,23 @@ class UploadFileHandler extends MessageHandler { ...@@ -244,19 +276,23 @@ class UploadFileHandler extends MessageHandler {
await Future.delayed(Duration(seconds: 2 * attempt)); await Future.delayed(Duration(seconds: 2 * attempt));
} }
} }
throw Exception('上传失败');
} }
/// 上传段 /// 上传段
static Future<Response> _uploadChunk( Future<Response> _uploadChunk(Dio dio,String signUrl, Uint8List chunk, int chunkIndex) async {
ApiService obsApiService, String signUrl, Uint8List chunk, int chunkIndex) async {
final start = DateTime.now().millisecondsSinceEpoch;
upLogger.d('分段$chunkIndex,处理开始时间 $start');
var url = signUrl.replaceFirst('AWSAccessKeyId=', 'AccessKeyId=').replaceFirst(':443', ''); var url = signUrl.replaceFirst('AWSAccessKeyId=', 'AccessKeyId=').replaceFirst(':443', '');
try { try {
Response response = await obsApiService.put(url, chunk); // Response response = await _put(url, chunk);
final end = DateTime.now().millisecondsSinceEpoch; print('====================> 分片$chunkIndex , 开始上传 ${DateTime.now()}');
upLogger.d('分段$chunkIndex,处理完成时间:${end - start}毫秒'); final response = await dio.put(
url,
// data: Stream.fromIterable(chunk.map((e) => [e])),
// data: Stream.fromIterable([chunk]),
data: chunk,
);
print('====================> 分片$chunkIndex , 上传成功 ${DateTime.now()}');
return response; return response;
} catch (e) { } catch (e) {
throw Exception('Chunk upload failed: $e'); throw Exception('Chunk upload failed: $e');
...@@ -264,7 +300,7 @@ class UploadFileHandler extends MessageHandler { ...@@ -264,7 +300,7 @@ class UploadFileHandler extends MessageHandler {
} }
/// 请求合并文件 /// 请求合并文件
static Future<String> _merge( Future<String> _merge(
ApiService bxeApiService, ApiService bxeApiService,
String objectKey, String objectKey,
String bucket, String bucket,
...@@ -290,7 +326,7 @@ class UploadFileHandler extends MessageHandler { ...@@ -290,7 +326,7 @@ class UploadFileHandler extends MessageHandler {
return response.data["location"]; return response.data["location"];
} }
static String _getLoginPrefix(String busi, String subBusi) { String _getLoginPrefix(String busi, String subBusi) {
var now = DateTime.now(); var now = DateTime.now();
var year = now.year; var year = now.year;
var month = now.month; var month = now.month;
...@@ -299,16 +335,17 @@ class UploadFileHandler extends MessageHandler { ...@@ -299,16 +335,17 @@ class UploadFileHandler extends MessageHandler {
return 'd2/pridel/user/$year$month$day/bxe/${busi}_$subBusi'; return 'd2/pridel/user/$year$month$day/bxe/${busi}_$subBusi';
} }
static String _addPreUrl(String location) { String _addPreUrl(String location) {
// /bxe-pics/d2/pridel/user/20251017/bxe/bxe_homework/f4ea233d-9e1b-4a3f-bc8f-b64e776f42a6.jpg // /bxe-pics/d2/pridel/user/20251017/bxe/bxe_homework/f4ea233d-9e1b-4a3f-bc8f-b64e776f42a6.jpg
if (location.startsWith('/bxe-files')) { if (location.startsWith('/bxe-files')) {
return 'https://files-obs.banxiaoer.com${location.substring(10)}'; return 'https://files-obs.banxiaoer.com${location.substring(10)}';
} else if (location.startsWith('/bxe-pics')) { } else if (location.startsWith('/bxe-pics')) {
return 'https://pics-obs.banxiaoer.com${location.substring(9)}'; return 'https://pics-obs.banxiaoer.com${location.substring(9)}';
} else if (location.startsWith('/bxe-videos')) { } else if (location.startsWith('/bxe-video')) {
return 'https://videos-obs.banxiaoer.com${location.substring(11)}'; return 'https://video-obs.banxiaoer.com${location.substring(10)}';
} else { } else {
return location; return location;
} }
} }
} }
...@@ -6,13 +6,10 @@ import 'package:appframe/services/dispatcher.dart'; ...@@ -6,13 +6,10 @@ import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart'; import 'package:appframe/utils/file_type_util.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
// import 'package:logger/logger.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class UploadFile2Handler extends MessageHandler { class UploadFile2Handler extends MessageHandler {
// static final upLogger = Logger(level: Level.debug);
@override @override
Future handleMessage(params) async { Future handleMessage(params) async {
if (params is! Map<String, dynamic>) { if (params is! Map<String, dynamic>) {
...@@ -34,16 +31,13 @@ class UploadFile2Handler extends MessageHandler { ...@@ -34,16 +31,13 @@ class UploadFile2Handler extends MessageHandler {
} }
final startTime = DateTime.now(); final startTime = DateTime.now();
// upLogger.d('开始上传文件,$startTime');
print('====================>开始上传文件,$startTime'); print('====================>开始上传文件,$startTime');
// final result = await compute(_handleUpload, {'filePath': tempFilePath, 'busi': busi, 'subBusi': subBusi}); // final result = await compute(_handleUpload, {'filePath': tempFilePath, 'busi': busi, 'subBusi': subBusi});
final result = await _handleUpload(tempFilePath, busi, subBusi); final result = await _handleUpload(tempFilePath, busi, subBusi);
final endTime = DateTime.now(); final endTime = DateTime.now();
// upLogger.d('完成上传文件,$endTime');
print('====================>完成上传文件,$endTime'); print('====================>完成上传文件,$endTime');
// upLogger.d('上传完成,耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
print('====================>上传完成,耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒'); print('====================>上传完成,耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
result['startTimestamp'] = startTime.toString(); result['startTimestamp'] = startTime.toString();
...@@ -63,8 +57,8 @@ class UploadFile2Handler extends MessageHandler { ...@@ -63,8 +57,8 @@ class UploadFile2Handler extends MessageHandler {
// String busi = fileParams['busi'] as String; // String busi = fileParams['busi'] as String;
// String subBusi = fileParams['subBusi'] as String; // String subBusi = fileParams['subBusi'] as String;
static Future<Map<String, dynamic>> _handleUpload(String filePath, String busi, String subBusi) async { static Future<Map<String, dynamic>> _handleUpload(String filePath, String busi, String subBusi) async {
if (filePath.startsWith(Constant.localServerUrl)) { if (filePath.startsWith(Constant.localFileUrl)) {
filePath = filePath.replaceFirst(Constant.localServerUrl, ''); filePath = filePath.replaceFirst(Constant.localFileUrl, '');
} }
if (filePath.startsWith(Constant.localServerTemp)) { if (filePath.startsWith(Constant.localServerTemp)) {
...@@ -80,13 +74,12 @@ class UploadFile2Handler extends MessageHandler { ...@@ -80,13 +74,12 @@ class UploadFile2Handler extends MessageHandler {
//并行上传分段 //并行上传分段
var startTime = DateTime.now(); var startTime = DateTime.now();
// upLogger.d("开始异步处理 $startTime");
print('====================>开始异步处理 $startTime'); print('====================>开始异步处理 $startTime');
final uploadResult = final uploadResult =
await _uploadInParallel(bxeApiService, obsApiService, logicPrefix, filePath, maxConcurrency: 30); await _uploadInParallel(bxeApiService, obsApiService, logicPrefix, filePath, maxConcurrency: 30);
var endTime = DateTime.now(); var endTime = DateTime.now();
// upLogger.d("完成异步处理 $endTime ,耗时 ${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒"); print(
print('====================>完成异步处理 $endTime ,耗时 ${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒'); '====================>完成异步处理 $endTime ,耗时 ${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
// 上传结果 // 上传结果
String objectKey = uploadResult['objectKey'] as String; String objectKey = uploadResult['objectKey'] as String;
...@@ -96,12 +89,11 @@ class UploadFile2Handler extends MessageHandler { ...@@ -96,12 +89,11 @@ class UploadFile2Handler extends MessageHandler {
//请求合并文件 //请求合并文件
var startTime2 = DateTime.now(); var startTime2 = DateTime.now();
// upLogger.d("开始处理合并文件 $startTime2");
print('====================>开始处理合并文件 $startTime2'); print('====================>开始处理合并文件 $startTime2');
String location = await _merge(bxeApiService, objectKey, bucket, uploadId, tagsMap); String location = await _merge(bxeApiService, objectKey, bucket, uploadId, tagsMap);
var endTime2 = DateTime.now(); var endTime2 = DateTime.now();
// upLogger.d("合并文件完成 $endTime2 ,耗时 ${endTime2.millisecondsSinceEpoch - startTime2.millisecondsSinceEpoch} 毫秒 "); print(
print('====================>合并文件完成 $endTime2 ,耗时 ${endTime2.millisecondsSinceEpoch - startTime2.millisecondsSinceEpoch} 毫秒 '); '====================>合并文件完成 $endTime2 ,耗时 ${endTime2.millisecondsSinceEpoch - startTime2.millisecondsSinceEpoch} 毫秒 ');
//关闭Dio //关闭Dio
bxeApiService.close(); bxeApiService.close();
...@@ -129,17 +121,14 @@ class UploadFile2Handler extends MessageHandler { ...@@ -129,17 +121,14 @@ class UploadFile2Handler extends MessageHandler {
throw Exception('上传的文件过大'); throw Exception('上传的文件过大');
} }
// upLogger.d('文件大小:$fileSize');
print('====================>文件大小:$fileSize'); print('====================>文件大小:$fileSize');
//分段大小2M //分段大小2M
final chunkSize = Constant.obsUploadChunkSize; final chunkSize = Constant.obsUploadChunkSize;
// upLogger.d('分段大小:$chunkSize');
print('====================>分段大小:$chunkSize'); print('====================>分段大小:$chunkSize');
//分段总数 //分段总数
final totalChunks = (fileSize / chunkSize).ceil(); final totalChunks = (fileSize / chunkSize).ceil();
// upLogger.d('分段总数:$totalChunks');
print('====================>分段总数:$totalChunks'); print('====================>分段总数:$totalChunks');
final randomAccessFile = file.openSync(); final randomAccessFile = file.openSync();
...@@ -163,15 +152,12 @@ class UploadFile2Handler extends MessageHandler { ...@@ -163,15 +152,12 @@ class UploadFile2Handler extends MessageHandler {
final futures = <Future>[]; final futures = <Future>[];
for (int i = 0; i < totalChunks; i++) { for (int i = 0; i < totalChunks; i++) {
// upLogger.d('开始处理分段:$i');
print('====================>开始处理分段:$i'); print('====================>开始处理分段:$i');
// 控制并发数量 // 控制并发数量
if (futures.length >= maxConcurrency) { if (futures.length >= maxConcurrency) {
// upLogger.d('超过最大并发数量,等待');
print('====================>超过最大并发数量,等待'); print('====================>超过最大并发数量,等待');
var resultList = await Future.wait(futures); var resultList = await Future.wait(futures);
// upLogger.d('等待完成');
print('====================>等待完成'); print('====================>等待完成');
for (var result in resultList) { for (var result in resultList) {
...@@ -201,7 +187,6 @@ class UploadFile2Handler extends MessageHandler { ...@@ -201,7 +187,6 @@ class UploadFile2Handler extends MessageHandler {
chunkSignUrl = nextResult['signed_url'] as String; chunkSignUrl = nextResult['signed_url'] as String;
} }
final endTime = DateTime.now().millisecondsSinceEpoch; final endTime = DateTime.now().millisecondsSinceEpoch;
// upLogger.d('分段$i,签名耗时:${endTime - startTime} 毫秒');
print('====================>分段$i,签名耗时:${endTime - startTime} 毫秒'); print('====================>分段$i,签名耗时:${endTime - startTime} 毫秒');
// await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap); // await _uploadChunkWithRetry(obsApiService, chunkSignUrl, i, chunk, tagsMap);
...@@ -222,7 +207,7 @@ class UploadFile2Handler extends MessageHandler { ...@@ -222,7 +207,7 @@ class UploadFile2Handler extends MessageHandler {
} }
} }
randomAccessFile.closeSync(); await randomAccessFile.close();
return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap}; return {'objectKey': objectKey, 'bucket': bucket, 'uploadId': uploadId, 'tagsMap': tagsMap};
} }
...@@ -263,11 +248,9 @@ class UploadFile2Handler extends MessageHandler { ...@@ -263,11 +248,9 @@ class UploadFile2Handler extends MessageHandler {
var maxRetries = 30; var maxRetries = 30;
final start = DateTime.now(); final start = DateTime.now();
// upLogger.d('new Isolate--- 分段$chunkIndex,开始时间:$start');
print('====================>new Isolate--- 分段$chunkIndex,开始时间:$start'); print('====================>new Isolate--- 分段$chunkIndex,开始时间:$start');
for (int attempt = 0; attempt <= maxRetries; attempt++) { for (int attempt = 0; attempt <= maxRetries; attempt++) {
// upLogger.d('new Isolate--- 分段$chunkIndex,第 ${attempt + 1} 次,开始时间:${DateTime.now()}');
print('====================>new Isolate--- 分段$chunkIndex,第 ${attempt + 1} 次,开始时间:${DateTime.now()}'); print('====================>new Isolate--- 分段$chunkIndex,第 ${attempt + 1} 次,开始时间:${DateTime.now()}');
try { try {
final obsApiService = ApiService(defaultHeaders: {'Content-Type': '', 'Accept': ''}); final obsApiService = ApiService(defaultHeaders: {'Content-Type': '', 'Accept': ''});
...@@ -276,9 +259,9 @@ class UploadFile2Handler extends MessageHandler { ...@@ -276,9 +259,9 @@ class UploadFile2Handler extends MessageHandler {
final etags = resp.headers['etag'] as List<String>; final etags = resp.headers['etag'] as List<String>;
// tagsMap[chunkIndex + 1] = etags[0]; // tagsMap[chunkIndex + 1] = etags[0];
final end = DateTime.now(); final end = DateTime.now();
// upLogger.d( print(
// 'new Isolate--- 分段$chunkIndex,完成时间: $end,上传段耗时:${end.millisecondsSinceEpoch - start.millisecondsSinceEpoch}毫秒'); '====================>new Isolate--- 分段$chunkIndex,完成时间: $end,上传段耗时:${end.millisecondsSinceEpoch - start.millisecondsSinceEpoch}毫秒');
print('====================>new Isolate--- 分段$chunkIndex,完成时间: $end,上传段耗时:${end.millisecondsSinceEpoch - start.millisecondsSinceEpoch}毫秒'); obsApiService.close();
return Future.value({'idx': chunkIndex + 1, 'etag': etags[0]}); // 上传成功 return Future.value({'idx': chunkIndex + 1, 'etag': etags[0]}); // 上传成功
} else { } else {
throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}'); throw Exception('Chunk $chunkIndex upload failed: ${resp.statusCode}');
...@@ -349,8 +332,8 @@ class UploadFile2Handler extends MessageHandler { ...@@ -349,8 +332,8 @@ class UploadFile2Handler extends MessageHandler {
return 'https://files-obs.banxiaoer.com${location.substring(10)}'; return 'https://files-obs.banxiaoer.com${location.substring(10)}';
} else if (location.startsWith('/bxe-pics')) { } else if (location.startsWith('/bxe-pics')) {
return 'https://pics-obs.banxiaoer.com${location.substring(9)}'; return 'https://pics-obs.banxiaoer.com${location.substring(9)}';
} else if (location.startsWith('/bxe-videos')) { } else if (location.startsWith('/bxe-video')) {
return 'https://videos-obs.banxiaoer.com${location.substring(11)}'; return 'https://video-obs.banxiaoer.com${location.substring(10)}';
} else { } else {
return location; return location;
} }
......
...@@ -6,14 +6,17 @@ class WechatAuthRepository { ...@@ -6,14 +6,17 @@ class WechatAuthRepository {
late final ApiService _apiService; late final ApiService _apiService;
WechatAuthRepository() { WechatAuthRepository() {
_apiService = getIt<ApiService>(); _apiService = getIt<ApiService>(instanceName: 'bxeApiService');
} }
Future<dynamic> codeToSk(String code) async { Future<dynamic> codeToSk(String code) async {
Response resp = await _apiService.post('/login/applet/wkbxe/codeToSkByApp?version=1.0.0', { Response resp = await _apiService.post(
'/login/applet/wkbxe/codeToSkByApp?version=1.0.0',
{
"appCode": "bxeapp", "appCode": "bxeapp",
"params": {"code": code}, "params": {"code": code},
}); },
);
print('登录结果: $resp'); print('登录结果: $resp');
......
import 'package:appframe/config/db.dart'; import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/mqtt.dart'; import 'package:appframe/services/im_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'app.dart'; import 'app.dart';
...@@ -8,8 +8,9 @@ import 'app.dart'; ...@@ -8,8 +8,9 @@ import 'app.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await setupLocator(); await setupLocator();
// await registerMqtt(); if (Constant.needIM) {
await initDatabase(); await getIt.get<ImService>().initSdk();
}
runApp(const App()); runApp(const App());
} }
...@@ -54,8 +54,7 @@ class MessageDispatcher { ...@@ -54,8 +54,7 @@ class MessageDispatcher {
h5Message.cmd == "chooseImage" || h5Message.cmd == "chooseImage" ||
h5Message.cmd == "chooseVideo" || h5Message.cmd == "chooseVideo" ||
h5Message.cmd == "goLogin" || h5Message.cmd == "goLogin" ||
h5Message.cmd.startsWith("audio") || h5Message.cmd.startsWith("setTitlebar")) {
h5Message.cmd.startsWith("setTitle")) {
handler.setCubit(webCubit!); handler.setCubit(webCubit!);
handler.setMessage(message); handler.setMessage(message);
} }
......
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_push/common/tim_push_listener.dart';
import 'package:tencent_cloud_chat_push/common/tim_push_message.dart';
import 'package:tencent_cloud_chat_push/tencent_cloud_chat_push.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimAdvancedMsgListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/V2TimSDKListener.dart';
import 'package:tencent_cloud_chat_sdk/enum/log_level_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/message_elem_type.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_receipt.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_status.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
class ImService {
// sdkListener 事件监听器
V2TimSDKListener sdkListener = V2TimSDKListener(
onConnectFailed: (int code, String error) {
// 连接失败的回调函数
// code 错误码
// error 错误信息
},
onConnectSuccess: () {
// SDK 已经成功连接到腾讯云服务器
},
onConnecting: () {
// SDK 正在连接到腾讯云服务器
},
onKickedOffline: () {
// 当前用户被踢下线,此时可以 UI 提示用户,并再次调用 V2TIMManager 的 login() 函数重新登录。
},
onSelfInfoUpdated: (V2TimUserFullInfo info) {
// 登录用户的资料发生了更新
// info登录用户的资料
},
onUserSigExpired: () {
// 在线时票据过期:此时您需要生成新的 userSig 并再次调用 V2TIMManager 的 login() 函数重新登录。
},
onUserStatusChanged: (List<V2TimUserStatus> userStatusList) {
//用户状态变更通知
//userStatusList 用户状态变化的用户列表
//收到通知的情况:订阅过的用户发生了状态变更(包括在线状态和自定义状态),会触发该回调
//在 IM 控制台打开了好友状态通知开关,即使未主动订阅,当好友状态发生变更时,也会触发该回调
//同一个账号多设备登录,当其中一台设备修改了自定义状态,所有设备都会收到该回调
},
);
// 消息监听器
final V2TimAdvancedMsgListener _msgListener = V2TimAdvancedMsgListener(
onRecvC2CReadReceipt: (List<V2TimMessageReceipt> receiptList) {
//单聊已读回调
},
onRecvMessageModified: (V2TimMessage message) {
// msg 为被修改之后的消息对象
},
onRecvMessageReadReceipts: (List<V2TimMessageReceipt> receiptList) {
//群聊已读回调
receiptList.forEach((element) {
element.groupID; // 群id
element.msgID; // 已读回执消息 ID
element.readCount; // 群消息最新已读数
element.unreadCount; // 群消息最新未读数
element.userID; // C2C 消息对方 ID
});
},
onRecvMessageRevoked: (String messageid) {
// 在本地维护的消息中处理被对方撤回的消息
},
onRecvNewMessage: (V2TimMessage message) async {
// 处理文本消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT) {
message.textElem?.text;
print("收到IM消息-------- ${message.textElem?.text}");
}
// 使用自定义消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) {
message.customElem?.data;
message.customElem?.desc;
message.customElem?.extension;
}
// 使用图片消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_IMAGE) {
message.imageElem?.path; // 图片上传时的路径,消息发送者才会有这个字段,消息发送者可用这个字段将图片预先上屏,优化上屏体验。
message.imageElem?.imageList?.forEach((element) {
// 遍历大图、原图、缩略图
// 解析图片属性
element?.height;
element?.localUrl;
element?.size;
element?.type; // 大图 缩略图 原图
element?.url;
element?.uuid;
element?.width;
});
}
// 处理视频消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) {
// 解析视频消息属性,封面、播放地址、宽高、大小等。
message.videoElem?.UUID;
message.videoElem?.duration;
message.videoElem?.localSnapshotUrl;
message.videoElem?.localVideoUrl;
message.videoElem?.snapshotHeight;
message.videoElem?.snapshotPath;
// ...
}
// 处理音频消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_SOUND) {
// 解析语音消息 播放地址,本地地址,大小,时长等。
message.soundElem?.UUID;
message.soundElem?.dataSize;
message.soundElem?.duration;
message.soundElem?.localUrl;
message.soundElem?.url;
// ...
}
// 处理文件消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_FILE) {
// 解析文件消息 文件名、文件大小、url等
message.fileElem?.UUID;
message.fileElem?.fileName;
message.fileElem?.fileSize;
message.fileElem?.localUrl;
message.fileElem?.path;
message.fileElem?.url;
}
// 处理位置消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_LOCATION) {
// 解析地理位置消息,经纬度、描述等
message.locationElem?.desc;
message.locationElem?.latitude;
message.locationElem?.longitude;
}
// 处理表情消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_FACE) {
message.faceElem?.data;
message.faceElem?.index;
}
// 处理群组tips文本消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS) {
message.groupTipsElem?.groupID; // 所属群组
message.groupTipsElem?.type; // 群Tips类型
message.groupTipsElem?.opMember; // 操作人资料
message.groupTipsElem?.memberList; // 被操作人资料
message.groupTipsElem?.groupChangeInfoList; // 群信息变更详情
message.groupTipsElem?.memberChangeInfoList; // 群成员变更信息
message.groupTipsElem?.memberCount; // 当前群在线人数
}
// 处理合并消息消息
if (message.elemType == MessageElemType.V2TIM_ELEM_TYPE_MERGER) {
message.mergerElem?.abstractList;
message.mergerElem?.isLayersOverLimit;
message.mergerElem?.title;
V2TimValueCallback<List<V2TimMessage>> download =
await TencentImSDKPlugin.v2TIMManager.getMessageManager().downloadMergerMessage(
msgID: message.msgID!,
);
if (download.code == 0) {
List<V2TimMessage>? messageList = download.data;
}
}
if (message.textElem?.nextElem != null) {
//通过第一个 Elem 对象的 nextElem 方法获取下一个 Elem 对象,如果下一个 Elem 对象存在,会返回 Elem 对象实例,如果不存在,会返回 null。
}
},
onSendMessageProgress: (V2TimMessage message, int progress) {
//文件上传进度回调
},
);
V2TimAdvancedMsgListener get msgListener => _msgListener;
Future<bool> initSdk() async {
// 初始化SDK
var initSDKRes = await TencentImSDKPlugin.v2TIMManager.initSDK(
sdkAppID: Constant.imSdkAppId,
loglevel: LogLevelEnum.V2TIM_LOG_ALL,
listener: sdkListener,
);
if (initSDKRes.code == 0) {
print("IM初始化成功--------");
return true;
} else {
print("IM初始化失败--------");
/// 失败后的处理,
return false;
}
}
///
/// 添加消息的事件监听器
///
Future<void> addMsgListener(V2TimAdvancedMsgListener listener) async {
await TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener(listener: listener);
}
///
/// 移除消息的事件监听器
///
Future<void> removeMsgListener(V2TimAdvancedMsgListener listener) async {
await TencentImSDKPlugin.v2TIMManager.getMessageManager().removeAdvancedMsgListener(listener: listener);
}
///
/// 登录 IM
///
Future<bool> login(String userID) async {
/*var userSig = GenerateTestUserSig(sdkappid: Constant.imSdkAppId, key: Constant.imAppSecure).genSig(
identifier: userID,
expire: 86400 * 30,
);*/
final apiService = getIt.get<ApiService>(instanceName: "appApiService");
var response = await apiService.get('/api/v1/im/sign', queryParameters: {'userID': userID});
if (response.statusCode != 200 || response.data['code'] != 0) {
return false;
}
var userSig = response.data['userSig'];
V2TimCallback res = await TencentImSDKPlugin.v2TIMManager.login(userID: userID, userSig: userSig);
if (res.code == 0) {
print("IM 登录成功--------");
// 添加消息的事件监听器
// await TencentImSDKPlugin.v2TIMManager.getMessageManager().addAdvancedMsgListener(listener: msgListener);
await addMsgListener(_msgListener);
return true;
} else {
// 登录失败逻辑
print("IM 登录失败--------");
return false;
}
}
/// 登出
///
Future<bool> logout() async {
var logoutRes = await TencentImSDKPlugin.v2TIMManager.logout();
if (logoutRes.code == 0) {
// 登出成功逻辑
print("IM 登出成功--------");
return true;
} else {
return false;
}
}
void _onNotificationClicked({required String ext, String? userID, String? groupID}) {
print("收到推送消息--------");
print("_onNotificationClicked: $ext, userID: $userID, groupID: $groupID");
if (userID != null || groupID != null) {
// 根据 userID 或 groupID 跳转至对应 Message 页面.
} else {
// 根据 ext 字段, 自己写解析方式, 跳转至对应页面.
}
}
TIMPushListener timPushListener = TIMPushListener(
onRecvPushMessage: (TimPushMessage message) {
print('推送监听器 onRecvPushMessage-------------');
String messageLog = message.toLogString();
debugPrint("message: $messageLog");
},
onRevokePushMessage: (String messageId) {
print('推送监听器 onRevokePushMessage-------------');
debugPrint("message: $messageId");
},
onNotificationClicked: (String ext) {
print('推送监听器 onNotificationClicked-------------');
debugPrint("ext: $ext");
},
);
/// 注册推送服务
///
Future<void> registerPush() async {
var res = await TencentCloudChatPush().registerPush(
onNotificationClicked: _onNotificationClicked,
sdkAppId: Constant.imSdkAppId,
appKey: Constant.imClientSecure,
);
if (res.code == 0) {
print('注册推送成功--------');
/// 添加监听器
///
TencentCloudChatPush().addPushListener(listener: timPushListener);
// var getIdRes = await TencentCloudChatPush().getRegistrationID();
// if (getIdRes.code == 0) {
// print('getRegistrationID: ${getIdRes.data}');
// } else {
// print('getRegistrationID: ${getIdRes.errorMessage}');
// }
//
// var tokenRes = await TencentCloudChatPush().getAndroidPushToken();
// if (tokenRes.code == 0) {
// print('android Token: ${tokenRes.data}');
// } else {
// print('android Token: ${tokenRes.errorMessage}');
// }
} else {
print('注册推送失败--------');
print('${res.errorMessage}');
}
}
}
import 'dart:io'; import 'dart:io';
import 'package:appframe/config/constant.dart'; import 'package:appframe/config/constant.dart';
import 'package:archive/archive.dart'; import 'package:appframe/config/locator.dart';
import 'package:dio/dio.dart'; import 'package:appframe/services/upgrade_service.dart';
import 'package:appframe/utils/zip_util.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LocalServerService { class LocalServerService {
String? _httpDirectory; String? _httpDirectory;
// 启动本地HTTP服务器 // 启动本地HTTP服务器
Future<HttpServer> startLocalServer() async { Future<HttpServer> startLocalServer() async {
// 检测和升级
// await getIt.get<UpgradeService>().upgrade();
// 测试情况下, 每次启动服务,先解压dist文件 // 测试情况下, 每次启动服务,先解压dist文件
_extractDist(); _extractDist();
...@@ -77,7 +82,7 @@ class LocalServerService { ...@@ -77,7 +82,7 @@ class LocalServerService {
Future<void> _serveHttpFile(HttpRequest request, String requestPath) async { Future<void> _serveHttpFile(HttpRequest request, String requestPath) async {
try { try {
var httpDirectory = await getHttpDirectory(); var httpDirectory = await _getHttpDirectory();
final String filePath = '$httpDirectory$requestPath'; final String filePath = '$httpDirectory$requestPath';
// 检查文件是否存在 // 检查文件是否存在
...@@ -136,7 +141,7 @@ class LocalServerService { ...@@ -136,7 +141,7 @@ class LocalServerService {
return 'application/octet-stream'; return 'application/octet-stream';
} }
Future<String> getHttpDirectory() async { Future<String> _getHttpDirectory() async {
if (_httpDirectory == null) { if (_httpDirectory == null) {
await _initHttpDirectory(); await _initHttpDirectory();
} }
...@@ -144,17 +149,18 @@ class LocalServerService { ...@@ -144,17 +149,18 @@ class LocalServerService {
} }
Future<void> _initHttpDirectory() async { Future<void> _initHttpDirectory() async {
var version = getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version;
if (Platform.isAndroid) { if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory(); var direct = await getExternalStorageDirectory();
_httpDirectory = '${direct?.path}/http_dist_assets'; _httpDirectory = '${direct?.path}/http_dist_assets_/$version';
} else if (Platform.isIOS || Platform.isMacOS) { } else if (Platform.isIOS || Platform.isMacOS) {
var direct = await getApplicationSupportDirectory(); var direct = await getApplicationSupportDirectory();
_httpDirectory = '${direct.path}/http_dist_assets'; _httpDirectory = '${direct.path}/http_dist_assets_/$version';
} }
} }
Future<void> _extractDist() async { Future<void> _extractDist() async {
var outputDirectory = await getHttpDirectory(); var outputDirectory = await _getHttpDirectory();
// 判断目录存在则不需要再解压 // 判断目录存在则不需要再解压
// if (Directory(outputDirectory).existsSync()) { // if (Directory(outputDirectory).existsSync()) {
...@@ -162,48 +168,6 @@ class LocalServerService { ...@@ -162,48 +168,6 @@ class LocalServerService {
// } // }
var zipFilePath = "assets/dist.zip"; var zipFilePath = "assets/dist.zip";
final ByteData data = await rootBundle.load(zipFilePath); ZipUtil.extractZipFile(zipFilePath, outputDirectory);
final bytes = data.buffer.asUint8List();
// 解码 ZIP 文件
final archive = ZipDecoder().decodeBytes(bytes);
// 提取文件到指定目录
for (final file in archive) {
final filename = file.name;
if (file.isFile) {
final data = file.content as List<int>;
final outputFile = File('$outputDirectory/$filename');
outputFile.createSync(recursive: true);
outputFile.writeAsBytesSync(data);
} else {
// 创建目录
Directory('$outputDirectory/$filename').createSync(recursive: true);
}
}
}
Future<void> _clearDist() async {
var outputDirectory = await getHttpDirectory();
Directory(outputDirectory).deleteSync(recursive: true);
}
void _downloadDist() async {
var httpDirectory = await getHttpDirectory();
var distUrl = "https://github.com/xinxin-wu/flutter_web_dist/releases/download/v1.0.0/dist.zip";
// Dio进行下载
var dio = Dio();
dio.download(
distUrl,
'$httpDirectory/dist.zip',
onReceiveProgress: (received, total) {
if (total != -1) {
print((received / total * 100).toStringAsFixed(0) + "%");
}
},
).then((_) {
_extractDist();
});
dio.close();
} }
} }
import 'dart:async';
import 'dart:io';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
class MqttService {
/// MQTT服务器信息
final String _host;
final int _port;
final String _clientId;
final int _keepAlive;
final String _username;
/// 重连的时候要重新获取一下 token
final String _password;
/// MQTT客户端
late MqttServerClient _client;
StreamSubscription? _subscription;
/// MQTT重连相关变量
int _reconnectAttempts = 0;
final int _maxReconnectAttempts = 5;
Timer? _reconnectTimer;
MqttService(this._host, this._port, this._clientId, this._keepAlive, this._username, this._password);
Future<void> initConn() async {
_client = MqttServerClient(_host, _clientId);
_client.logging(on: false);
_client.port = _port;
_client.keepAlivePeriod = _keepAlive;
_client.onDisconnected = onDisconnected;
_client.onConnected = onConnected;
_client.onSubscribed = onSubscribed;
_client.pongCallback = pong;
final connMess = MqttConnectMessage()
.withWillTopic('willtopic')
.withWillMessage('My Will message')
.startClean()
.withWillQos(MqttQos.atLeastOnce);
_client.connectionMessage = connMess;
try {
print('MQTT 开始连接......');
await _client.connect(_username, _password);
} on NoConnectionException catch (e) {
print('MQTT客户端连接失败: $e');
_client.disconnect();
} on SocketException catch (e) {
print('MQTT客户端连接失败: $e');
_client.disconnect();
}
// 订阅主题
subscribe("\$q/bxe/abc");
// 监听消息
listen(onMessageReceived);
}
void onConnected() {
print('MQTT客户端连接成功');
_reconnectAttempts = 0;
_reconnectTimer?.cancel();
}
void onDisconnected() {
print('MQTT客户端断开连接');
// if (_reconnectAttempts < _maxReconnectAttempts) {
// handleReconnect(_host, _port, _clientId, '');
// }
}
void onSubscribed(String topic) {
print('MQTT客户端订阅主题: $topic');
}
void onMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('MQTT消息接收: topic is ${c[0].topic}, payload is $pt');
// 处理接收到的消息
}
void subscribe(String topic) {
_client.subscribe(topic, MqttQos.atLeastOnce);
}
void listen(Function(List<MqttReceivedMessage<MqttMessage?>>? c) onMessageReceivedCallback) {
_subscription?.cancel();
_subscription = _client.updates?.listen(onMessageReceivedCallback);
}
void disconnect() {
_client.disconnect();
}
bool isConnected() {
return _client.connectionStatus?.state == MqttConnectionState.connected;
}
void pong() {
print('Ping response client callback invoked');
}
void onMqttMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('MQTT消息接收: topic is ${c[0].topic}, payload is $pt');
// 处理接收到的消息
}
void handleReconnect(String host, int port, String username, String password) {
if (_reconnectAttempts >= _maxReconnectAttempts) {
print('已达到最大重连次数,停止重连');
return;
}
_reconnectAttempts++;
print('第 $_reconnectAttempts 次重连尝试,${_maxReconnectAttempts - _reconnectAttempts} 次机会剩余');
// 取消之前的重连定时器(如果有)
_reconnectTimer?.cancel();
// 设置延迟重连,每次重连间隔递增
final delay = Duration(seconds: _reconnectAttempts * 5);
_reconnectTimer = Timer(delay, () async {
print('正在尝试重新连接...');
await initConn();
if (!isConnected()) {
disconnect();
}
});
}
}
import 'dart:async';
import 'package:flutter_sound/flutter_sound.dart';
class PlayerService {
/// 播放器
FlutterSoundPlayer? _player;
///
StreamSubscription? _playerSubscription;
/// 播放器是否初始化
bool? _playerIsInit;
/// 播放指令传递的 playId
String? _playId;
/// 当前播放文件的时长
int? _playDuration;
Function? _sendResponse;
set sendResponse(Function value) {
_sendResponse = value;
}
Future<bool> _initPlayer() async {
// 打开播放器
try {
final player = FlutterSoundPlayer();
_player = (await player.openPlayer())!;
_player!.setSpeed(1); // 播放速度,默认1
// 播放进度回调
_player!.setSubscriptionDuration(Duration(seconds: 1));
_playerSubscription = _player!.onProgress!.listen((event) {
_playDuration = event.duration.inSeconds;
var playPosition = event.position.inSeconds;
var h5Cmd = {
'unique': '',
'cmd': 'audioProgress',
'data': {'playId': _playId, 'duration': _playDuration, 'currentTime': playPosition},
'errMsg': '',
};
_sendResponse!(h5Cmd);
});
_playerIsInit = true;
return true;
} catch (e) {
throw Exception('打开播放器失败!');
}
}
Future<bool> playAudio(String url, int seek, String playId) async {
if (!(_playerIsInit ?? false)) {
final initResult = await _initPlayer();
if (!initResult) {
return false;
}
}
_playId = playId;
await _player!.startPlayer(
fromURI: url,
whenFinished: () async {
await _player!.stopPlayer();
// 补发一下全部进度
var h5Cmd = {
'unique': '',
'cmd': 'audioProgress',
'data': {'playId': _playId, 'duration': _playDuration, 'currentTime': _playDuration},
'errMsg': '',
};
_sendResponse!(h5Cmd);
// 播放结束后,发送消息给客户端
h5Cmd = {
'unique': '',
'cmd': 'audioEnd',
'data': {'playId': _playId},
'errMsg': '',
};
_sendResponse!(h5Cmd);
},
);
if (seek != 0) {
await seekAudio(seek);
}
return true;
}
Future<bool> pauseAudio() async {
if (!(_playerIsInit ?? false)) {
throw Exception("播放器未初始化");
}
if (!_player!.isPlaying) {
throw Exception("播放器状态错误");
}
await _player!.pausePlayer();
return true;
}
Future<bool> resumeAudio() async {
if (!(_playerIsInit ?? false)) {
throw Exception("播放器未初始化");
}
if (!_player!.isPaused) {
throw Exception("播放器状态错误");
}
await _player!.resumePlayer();
return true;
}
Future<bool> seekAudio(int seek) async {
if (!(_playerIsInit ?? false)) {
throw Exception("播放器未初始化");
}
await _player!.seekToPlayer(Duration(seconds: seek));
return true;
}
Future<bool> stopAudio() async {
if (!(_playerIsInit ?? false)) {
throw Exception("播放器未初始化");
}
if (!_player!.isPlaying && !_player!.isPaused) {
throw Exception("播放器状态错误");
}
await _player!.stopPlayer();
return true;
}
Future<bool> clearAudio() async {
try {
await _player?.closePlayer();
_player = null;
await _playerSubscription?.cancel();
_playerSubscription = null;
} catch (e) {
print(e);
}
_playerIsInit = false;
_playId = '';
_playDuration = null;
return true;
}
///
/// 清理
///
Future<void> close() async {
try {
await _player?.closePlayer();
_player = null;
await _playerSubscription?.cancel();
_playerSubscription = null;
} catch (e) {
print(e);
}
_playerIsInit = false;
_playId = '';
_playDuration = null;
}
}
import 'dart:async';
import 'package:appframe/config/constant.dart';
import 'package:appframe/utils/audio_util.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
class RecorderService {
FlutterSoundRecorder? _recorder;
StreamSubscription? _recorderSubscription;
bool? _recorderIsInit;
String? _recordPath;
Future<bool> _initRecorder(int maxDuration) async {
// 请求麦克风权限
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('no auth');
}
final directory = await getTemporaryDirectory();
String recordPath = '${directory.path}/${Uuid().v4()}_record.mp4';
// 打开录音器
try {
final recorder = FlutterSoundRecorder();
_recorder = (await recorder.openRecorder())!;
if (maxDuration > 0) {
// 设置进度回调间隔
await _recorder!.setSubscriptionDuration(Duration(seconds: 1));
// 监听录制进度
_recorderSubscription = _recorder!.onProgress!.listen((event) {
// event.duration 包含当前录制时长
// event.decibels 包含当前音量级别
print('录制进度: ${event.duration.inSeconds}秒, 音量: ${event.decibels}');
/*if (event.duration.inSeconds >= maxDuration) {
stopRecording();
}*/
});
}
_recorderIsInit = true;
_recordPath = recordPath;
return true;
} catch (e) {
throw Exception('打开录音器失败!');
}
}
Future<bool> startRecording(int maxDuration) async {
if (_recorderIsInit ?? false) {
return false;
}
if (_recorder != null && !_recorder!.isStopped) {
return false;
}
final initResult = await _initRecorder(maxDuration);
if (!initResult) {
return false;
}
await _recorder!.startRecorder(toFile: _recordPath, codec: Codec.aacMP4);
return true;
}
Future<bool> pauseRecording() async {
if (!(_recorderIsInit ?? false)) {
return false;
}
if (!_recorder!.isRecording) {
return false;
}
await _recorder!.pauseRecorder();
return true;
}
Future<bool> resumeRecording() async {
if (!(_recorderIsInit ?? false)) {
return false;
}
if (!_recorder!.isPaused) {
return false;
}
await _recorder!.resumeRecorder();
return true;
}
Future<Map<String, dynamic>> stopRecording() async {
if (!(_recorderIsInit ?? false)) {
throw Exception("录音器未初始化");
}
if (!_recorder!.isRecording && !_recorder!.isPaused) {
throw Exception("录音器状态错误");
}
var url = await _recorder!.stopRecorder();
await _recorder!.closeRecorder();
_recorder = null;
_recorderIsInit = false;
_recordPath = '';
if (url == null || url.isEmpty) {
throw Exception("录音失败");
}
var tempDir = await getTemporaryDirectory();
String fileName = path.basenameWithoutExtension(url);
String mp3Path = '${tempDir.path}/$fileName.mp3';
// var convertResult = await compute(AudioUtil.convertAacToMp3, {'accPath': url, 'mp3Path': mp3Path});
var convertResult = await AudioUtil.convertAacToMp3({'accPath': url, 'mp3Path': mp3Path});
if (!convertResult) {
throw Exception("录音转码失败");
}
// 时长
// var duration = await AudioUtil.getAudioDuration(mp3Path);
var duration = await AudioUtil.getAudioDuration(url);
return {
'tempFilePath': '${Constant.localServerTempFileUrl}$mp3Path',
'duration': duration.inSeconds,
};
}
Future<bool> clearRecording() async {
// await _recorder!.stopRecorder();
try {
await _recorder?.closeRecorder();
_recorder = null;
await _recorderSubscription?.cancel();
_recorderSubscription = null;
} catch (e) {
print(e);
}
_recorderIsInit = false;
_recordPath = '';
return true;
}
Future<void> close() async {
try {
await _recorder?.closeRecorder();
_recorder = null;
await _recorderSubscription?.cancel();
_recorderSubscription = null;
} catch (e) {
print(e);
}
_recorderIsInit = false;
_recordPath = '';
}
}
import 'dart:io';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/utils/zip_util.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
class UpgradeService {
String configUrl = "https://bxe-obs.banxiaoer.com/conf/xeapp_conf_dev.json";
Future<void> upgrade() async {
await _handleUpgrade();
}
Future<bool> _handleUpgrade() async {
Dio dio = Dio();
try {
Response response = await dio.get('$configUrl?t=${DateTime.now().millisecondsSinceEpoch}');
if (response.statusCode != 200) {
return false;
}
String version = response.data['version'] as String;
String zip = response.data['zip'] as String;
// 版本号相同,则不需要升级
if (version == (getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version)) {
print('版本号相同,不需要升级');
return true;
}
// 下载zip文件
var tempDir = await getTemporaryDirectory();
var saveFilePath = '${tempDir.path}/${Uuid().v4()}.zip';
var downloadResult = await _downloadFile(dio, '$zip$version.zip', saveFilePath);
if (!downloadResult) {
return false;
}
// 解压zip文件
String? httpDirect;
if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory();
httpDirect = '${direct?.path}/http_dist_assets_/$version';
} else if (Platform.isIOS || Platform.isMacOS) {
var direct = await getApplicationSupportDirectory();
httpDirect = '${direct.path}/http_dist_assets_/$version';
}
var result = await ZipUtil.extractZipFile(saveFilePath, httpDirect!);
if (!result) {
return false;
}
// 设置版本标识
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.setString('h5_version', version);
return true;
} catch (e) {
print('升级请求失败: $e');
return false;
} finally {
// 请求结束
dio.close(force: true);
}
}
Future<bool> _downloadFile(Dio dio, String url, String savePath) async {
try {
Response response = await dio.download(url, savePath);
if (response.statusCode == 200) {
print('文件下载成功: $savePath');
return true;
} else {
print('文件下载失败: ${response.statusCode}');
return false;
}
} catch (e) {
print('文件下载失败: $e');
return false;
}
}
}
import 'package:appframe/bloc/mqtt_cubit.dart'; import 'package:appframe/bloc/im_cubit.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class MqttPage extends StatelessWidget { class ImPage extends StatelessWidget {
const MqttPage({super.key}); const ImPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => MqttCubit(), create: (context) => ImCubit(),
child: BlocConsumer<MqttCubit, MqttState>( child: BlocConsumer<ImCubit, ImState>(
builder: (context, state) { builder: (context, state) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('推送测试'), title: Text('消息测试'),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back), icon: Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
context.read<MqttCubit>().goWeb(); context.read<ImCubit>().goWeb();
}, },
), ),
), ),
......
...@@ -51,108 +51,62 @@ class WebPage extends StatelessWidget { ...@@ -51,108 +51,62 @@ class WebPage extends StatelessWidget {
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(state.title), title: Text(state.title, style: TextStyle(color: Color(state.titleColor), fontSize: 18)),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
leading: state.beBack backgroundColor: Color(state.bgColor),
actionsIconTheme: IconThemeData(color: Colors.white),
// leading: state.beBack
// ? IconButton(
// icon: const Icon(Icons.arrow_back, color: Colors.white),
// onPressed: () {
// ctx.read<WebCubit>().handleBack();
// },
// )
// : null,
leading: state.opIcon == 'back'
? IconButton( ? IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () { onPressed: () {
ctx.read<WebCubit>().handleBack(); ctx.read<WebCubit>().handleBack();
}, },
) )
: null, : (state.opIcon == 'home'
// actions: [ ? IconButton(
// Builder( icon: const Icon(Icons.home, color: Colors.white),
// builder: (BuildContext context) { onPressed: () {
// return IconButton( ctx.read<WebCubit>().handleHome();
// icon: const Icon(Icons.more_vert), },
// onPressed: () { )
// showModalBottomSheet( : null),
// context: context,
// builder: (BuildContext context) {
// return SizedBox(
// height: 300,
// child: Column(
// children: [
// ListTile(
// leading: const Icon(Icons.chat_outlined),
// title: const Text('微信授权'),
// onTap: () {
// Navigator.pop(context);
// ctx.read<WebCubit>().goWechatAuth();
// },
// ),
// ListTile(
// leading: const Icon(Icons.accessibility_new),
// title: const Text('身份认证'),
// onTap: () {
// Navigator.pop(context);
// ctx.read<WebCubit>().goAuth();
// },
// ),
// ListTile(
// leading: const Icon(Icons.app_blocking_sharp),
// title: const Text('打开小程序'),
// onTap: () {
// Navigator.pop(context);
// ctx.read<WebCubit>().goMiniProgram();
// },
// ),
// ListTile(
// leading: const Icon(Icons.refresh),
// title: const Text('刷新'),
// onTap: () {
// Navigator.pop(context);
// ctx.read<WebCubit>().refresh();
// },
// ),
// ListTile(
// leading: const Icon(Icons.cleaning_services),
// title: const Text('清理缓存'),
// onTap: () {
// Navigator.pop(context);
// ctx.read<WebCubit>().clearStorage();
// },
// ),
// ],
// ),
// );
// },
// );
// },
// );
// },
// ),
// ],
), ),
endDrawer: Drawer( endDrawer: Drawer(
width: MediaQuery.of(ctx).size.width * 0.8, width: MediaQuery.of(ctx).size.width * 0.8,
child: ListView(padding: EdgeInsets.zero, children: [ child: ListView(padding: EdgeInsets.zero, children: [
// DrawerHeader( DrawerHeader(
// decoration: BoxDecoration( decoration: BoxDecoration(
// color: Theme.of(ctx).colorScheme.primary, color: Color(0xFF7691FA),
// ), ),
// child: Text('设置', child: Text('设置',
// style: TextStyle( style: TextStyle(
// color: Theme.of(ctx).colorScheme.onPrimary, // color: Theme.of(ctx).colorScheme.onPrimary,
// fontSize: 24, fontSize: 24,
// ))), color: Colors.white,
ListTile( ))),
/*ListTile(
tileColor: Color(0xFF7691FA), tileColor: Color(0xFF7691FA),
title: Text('设置', title: Text('设置',
style: TextStyle( style: TextStyle(
color: Theme.of(ctx).colorScheme.onPrimary, color: Theme.of(ctx).colorScheme.onPrimary,
fontSize: 24, fontSize: 24,
) )),
), ),*/
),
ListTile( ListTile(
leading: const Icon(Icons.chat_bubble_outline), leading: const Icon(Icons.chat_bubble_outline),
title: const Text('推送测试'), title: const Text('消息测试'),
onTap: () { onTap: () {
Navigator.pop(ctx); Navigator.pop(ctx);
ctx.read<WebCubit>().goMqtt(); ctx.read<WebCubit>().goIm();
}, },
), ),
ListTile( ListTile(
...@@ -192,7 +146,8 @@ class WebPage extends StatelessWidget { ...@@ -192,7 +146,8 @@ class WebPage extends StatelessWidget {
title: const Text('刷新'), title: const Text('刷新'),
onTap: () { onTap: () {
Navigator.pop(ctx); Navigator.pop(ctx);
ctx.read<WebCubit>().refresh(); // ctx.read<WebCubit>().refresh();
ctx.read<WebCubit>().handleRefreshPage();
}, },
), ),
ListTile( ListTile(
...@@ -203,13 +158,52 @@ class WebPage extends StatelessWidget { ...@@ -203,13 +158,52 @@ class WebPage extends StatelessWidget {
ctx.read<WebCubit>().clearStorage(); ctx.read<WebCubit>().clearStorage();
}, },
), ),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('退出登录'),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().logout();
},
),
])), ])),
body: state.loaded body: state.loaded
? SizedBox( ? SizedBox(
height: MediaQuery.of(ctx).size.height - 120, // 减去100像素留空 height: MediaQuery.of(ctx).size.height - 60, // 减去100像素留空
child: WebViewWidget(controller: ctx.read<WebCubit>().controller), child: WebViewWidget(controller: ctx.read<WebCubit>().controller),
) )
: const Center(child: CircularProgressIndicator()), : const Center(child: CircularProgressIndicator()),
bottomNavigationBar: state.showBottomNavBar
? BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: state.selectedIndex,
selectedItemColor: Color(0xFF7691fa),
unselectedItemColor: Color(0xFF969799),
onTap: (index) {
// 更新选中索引
ctx.read<WebCubit>().updateSelectedIndex(index);
// 根据 index 执行相应的操作
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home, size: 32),
label: '我的班级',
),
BottomNavigationBarItem(
icon: Icon(Icons.contact_page, size: 32),
label: '通讯录',
),
BottomNavigationBarItem(
icon: Icon(Icons.find_in_page, size: 32),
label: '发现',
),
BottomNavigationBarItem(
icon: Icon(Icons.person, size: 32),
label: '我的',
),
],
)
: null,
), ),
); );
}, },
...@@ -228,19 +222,3 @@ class WebPage extends StatelessWidget { ...@@ -228,19 +222,3 @@ class WebPage extends StatelessWidget {
); );
} }
} }
class LoginMainState {
final bool loaded;
final bool orientationCmdFlag;
final bool windowInfoCmdFlag;
final bool chooseImageCmdFlag;
final bool chooseVideoCmdFlag;
LoginMainState({
required this.loaded,
required this.orientationCmdFlag,
required this.windowInfoCmdFlag,
required this.chooseImageCmdFlag,
required this.chooseVideoCmdFlag,
});
}
import 'dart:convert'; import 'dart:convert';
import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit.dart'; import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new_audio/ffprobe_kit.dart'; import 'package:ffmpeg_kit_flutter_new/ffprobe_kit.dart';
import 'package:ffmpeg_kit_flutter_new_audio/return_code.dart'; import 'package:ffmpeg_kit_flutter_new/return_code.dart';
class AudioUtil { class AudioUtil {
/// 转码 /// 转码
......
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
class VideoUtil {
///
/// 将视频格式转换为mp4
/// 转码的同时,进行压缩
///
static Future<bool> convertToMp4(String inputPath, String outputPath) async {
// final session = await FFmpegKit.execute(
// '-i "$inputPath" -c:v libx264 -c:a aac -strict experimental -movflags faststart -f mp4 "$outputPath"');
final session = await FFmpegKit.execute(
'-i "$inputPath" -c:v libx264 -crf 28 -c:a aac -b:a 128k -strict experimental -movflags faststart -f mp4 "$outputPath"');
final returnCode = await session.getReturnCode();
return ReturnCode.isSuccess(returnCode);
}
///
/// 通过 ffmpeg 压缩视频
///
static Future<bool> compressVideo(String inputPath, String outputPath, String quality) async {
// 使用CRF模式进行压缩,值范围0-51,建议值18-28
// 高质量: CRF 18-20
// 中等质量: CRF 23-26
// 低质量: CRF 28-32
int crf;
switch (quality) {
case 'low':
crf = 32;
break;
case 'middle':
crf = 26;
break;
case 'high':
crf = 20;
break;
default:
throw Exception('参数错误');
}
final session = await FFmpegKit.execute(
'-i "$inputPath" -c:v libx264 -crf $crf -c:a aac -b:a 128k -preset medium -movflags faststart "$outputPath"');
final returnCode = await session.getReturnCode();
return ReturnCode.isSuccess(returnCode);
}
}
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/services.dart';
class ZipUtil {
static Future<bool> extractZipFile(String zipFilePath, String extractPath) async {
try {
// 读取zip文件
Uint8List bytes;
if (zipFilePath.startsWith('assets/')) {
// 读取 assets 中的文件
final ByteData data = await rootBundle.load(zipFilePath);
bytes = data.buffer.asUint8List();
} else {
// 读取本地文件
final zipFile = File(zipFilePath);
bytes = await zipFile.readAsBytes();
}
// 解压ZIP文件
final archive = ZipDecoder().decodeBytes(bytes);
// 创建解压目录
final directory = Directory(extractPath);
if (!(await directory.exists())) {
await directory.create(recursive: true);
}
// 遍历并提取文件
for (final file in archive) {
final filename = file.name;
if (file.isFile) {
final data = file.content as List<int>;
final outputFile = File('$extractPath/$filename');
// 创建父目录
await outputFile.create(recursive: true);
// 写入文件内容
await outputFile.writeAsBytes(data);
} else {
// 创建目录
final dir = Directory('$extractPath/$filename');
await dir.create(recursive: true);
}
}
print('文件解压成功: $extractPath');
return true;
} catch (e) {
print('文件解压失败: $e');
return false;
}
}
}
...@@ -43,9 +43,12 @@ dependencies: ...@@ -43,9 +43,12 @@ dependencies:
connectivity_plus: ^7.0.0 connectivity_plus: ^7.0.0
device_info_plus: ^11.5.0 device_info_plus: ^11.5.0
dio: ^5.9.0 dio: ^5.9.0
# dio_http2_adapter: ^2.6.0
equatable: ^2.0.7 equatable: ^2.0.7
exif: ^3.3.0 exif: ^3.3.0
ffmpeg_kit_flutter_new_audio: ^1.1.0 # ffmpeg_kit_flutter_new_audio: ^1.1.0
# ffmpeg_kit_flutter_new_video: ^1.1.0
ffmpeg_kit_flutter_new: ^3.2.0
file_picker: ^10.3.2 file_picker: ^10.3.2
flutter_bloc: ^9.1.1 flutter_bloc: ^9.1.1
flutter_localization: ^0.3.3 flutter_localization: ^0.3.3
...@@ -63,14 +66,14 @@ dependencies: ...@@ -63,14 +66,14 @@ dependencies:
logger: ^2.6.2 logger: ^2.6.2
mime: ^2.0.0 mime: ^2.0.0
mobile_scanner: ^7.0.1 mobile_scanner: ^7.0.1
mqtt_client: ^10.11.1 # mqtt_client: ^10.11.1
network_info_plus: ^7.0.0 network_info_plus: ^7.0.0
open_file: ^3.5.10 open_file: ^3.5.10
path: ^1.9.1 path: ^1.9.1
path_provider: ^2.1.5 path_provider: ^2.1.5
permission_handler: ^12.0.1 permission_handler: ^12.0.1
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
sqflite: ^2.4.2 # sqflite: ^2.4.2
# video_thumbnail: ^0.5.6 # video_thumbnail: ^0.5.6
url_launcher: ^6.3.2 url_launcher: ^6.3.2
uuid: ^4.5.1 uuid: ^4.5.1
...@@ -80,6 +83,11 @@ dependencies: ...@@ -80,6 +83,11 @@ dependencies:
webview_flutter: ^4.13.0 webview_flutter: ^4.13.0
wechat_assets_picker: ^9.8.0 wechat_assets_picker: ^9.8.0
wechat_camera_picker: ^4.4.0 wechat_camera_picker: ^4.4.0
# squadron: ^7.2.0
tencent_cloud_chat_sdk: ^8.7.7201+2
tencent_cloud_chat_push: ^8.7.7201
# sig test
# crypto: ^3.0.7
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
...@@ -90,6 +98,8 @@ dev_dependencies: ...@@ -90,6 +98,8 @@ dev_dependencies:
sdk: flutter sdk: flutter
build_runner: ^2.7.0 build_runner: ^2.7.0
json_serializable: ^6.11.0 json_serializable: ^6.11.0
flutter_launcher_icons: ^0.14.4
# squadron_builder: ^8.0.0+1
# The "flutter_lints" package below contains a set of recommended lints to # The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is # encourage good coding practices. The lint set provided by the package is
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!