Commit 6608a3d6 by Administrator

Merge branch 'develop251027'

合并代码内容

# Conflicts:
#	lib/bloc/web_cubit.dart
#	lib/config/locator.dart
#	lib/config/wechat.dart
2 parents 123edb66 efd1f3ba
Showing 67 changed files with 1276 additions and 655 deletions
......@@ -7,6 +7,7 @@ flutter clean
flutter pub get
flutter run -d macos
flutter run -d 00008030-001C75810E42402E --release
flutter run -d 00008030-001C75810E42402E --release
......@@ -72,6 +72,11 @@ android {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
......
# 微信SDK混淆规则
-keep class com.tencent.mm.opensdk.** {
*;
}
-keep class com.tencent.wxop.** {
*;
}
-keep class com.tencent.mm.sdk.** {
*;
}
# ffmpegkit
-keep class com.antonkarpenko.**{
*;
}
# 可选:如果遇到兼容性问题,可以忽略警告
-dontwarn com.tencent.mm.**
\ No newline at end of file
No preview for this file type
import 'dart:async';
import 'package:appframe/config/routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AdvState extends Equatable {
final int countdown;
const AdvState({this.countdown = 5});
AdvState copyWith({
int? countdown,
}) {
return AdvState(
countdown: countdown ?? this.countdown,
);
}
@override
List<Object?> get props => [countdown];
}
class AdvCubit extends Cubit<AdvState> {
Timer? _timer;
AdvCubit() : super(AdvState()) {
_startCountdown();
}
void _startCountdown() {
var count = state.countdown;
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
count--;
if (count <= 0) {
_timer?.cancel();
router.go('/web');
} else {
emit(state.copyWith(countdown: count));
}
});
}
@override
Future<void> close() {
try {
_timer?.cancel();
} catch (e) {
print(e);
}
return super.close();
}
}
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/locator.dart';
class LoginMainState extends Equatable {
final bool agreed;
final bool showAgreed;
const LoginMainState({this.agreed = false, this.showAgreed = false});
@override
List<Object?> get props => [agreed, showAgreed];
LoginMainState copyWith({
bool? agreed,
bool? showAgreed,
}) {
return LoginMainState(
agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed,
);
}
}
class LoginMainCubit extends Cubit<LoginMainState> {
late final Fluwx _fluwx;
late final WechatAuthRepository _wechatAuthRepository;
LoginMainCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>();
_fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt<WechatAuthRepository>();
}
void toggleAgreed(bool value) {
emit(state.copyWith(agreed: value));
}
void confirmAgreed() {
emit(state.copyWith(agreed: true, showAgreed: false));
wechatAuth();
}
void cancelAgreed() {
emit(state.copyWith(showAgreed: false));
}
void wechatAuth() async {
if (!state.agreed) {
emit(state.copyWith(showAgreed: true));
return;
}
var result = await _fluwx.authBy(
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
);
if (!result) {
throw Exception('微信授权处理失败');
}
}
void goLoginPhone() {
router.go('/loginPhone');
}
void _responseListener(WeChatResponse response) async {
if (response is WeChatAuthResponse) {
if (response.code == null || response.code == '') {
return;
}
dynamic resultData = await _wechatAuthRepository.codeToSk(response.code!);
var data = resultData['data'];
var role = data['roles'][0];
final sessionCode = data['sessionCode'];
final userCode = data['userCode'];
final classCode = role['classCode'];
final userType = role['userType'];
final stuId = role['stuId'];
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId ?? '');
sharedPreferences.setString('auth_ip', Constant.h5Server);
router.go(
'/web',
extra: {
'ip': Constant.h5Server,
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
}
}
}
import 'dart:async';
import 'package:appframe/config/routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class LoginPhoneState extends Equatable {
final bool agreed;
final bool showAgreed;
final String phone;
final String password;
final bool allowSend;
final int seconds;
const LoginPhoneState(
{this.agreed = false,
this.showAgreed = false,
this.phone = '',
this.password = '',
this.allowSend = true,
this.seconds = 0});
LoginPhoneState copyWith({
bool? agreed,
bool? showAgreed,
String? phone,
String? password,
bool? allowSend,
int? seconds,
}) {
return LoginPhoneState(
agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed,
phone: phone ?? this.phone,
password: password ?? this.password,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
);
}
@override
List<Object?> get props => [phone, password, agreed, showAgreed, allowSend, seconds];
}
class LoginPhoneCubit extends Cubit<LoginPhoneState> {
late TextEditingController _phoneController;
late TextEditingController _codeController;
Timer? _timer;
int countdown = 60; // 倒计时秒数
TextEditingController get phoneController => _phoneController;
TextEditingController get codeController => _codeController;
LoginPhoneCubit(super.initialState) {
_phoneController = TextEditingController();
_codeController = TextEditingController();
_phoneController.text = state.phone;
_codeController.text = state.password;
}
/// 开始倒计时
void startCountdown() {
countdown = 60;
emit(state.copyWith(allowSend: false, seconds: countdown));
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
countdown--;
if (countdown <= 0) {
_timer?.cancel();
emit(state.copyWith(allowSend: true, seconds: 60));
} else {
emit(state.copyWith(seconds: countdown));
}
});
}
/// 发送验证码
void sendVerificationCode() {
if (state.allowSend) {
// 实际发送验证码的逻辑
startCountdown(); // 开始倒计时
}
}
void toggleAgreed(bool value) {
emit(state.copyWith(agreed: value));
}
void goLoginMain() {
router.go('/loginMain');
}
@override
Future<void> close() async {
try {
_phoneController.dispose();
} catch (e) {
print(e);
}
try {
_codeController.dispose();
} catch (e) {
print(e);
}
await 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();
}
}
......@@ -197,17 +197,21 @@ class WebCubit extends Cubit<WebState> {
// closeLocalPlayer();
},
onPageFinished: (String url) async {
_controller.runJavaScript(
'document.querySelector("meta[name=viewport]").setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")',
);
finishLoading();
print('onPageFinished--------------------------------->');
print(url);
if (url == '${Constant.localServerTestFileUrl}/login.html') {
return;
}
// 页面加载完成时,清空录音和音频(如果有打开过)
await clearRecording();
await clearAudio();
print('onPageFinished--------------------------------->');
print(url);
_controller.runJavaScript(
'document.querySelector("meta[name=viewport]").setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no")',
);
finishLoading();
},
),
)
......@@ -234,8 +238,10 @@ class WebCubit extends Cubit<WebState> {
}
// serverUrl = '${Constant.localServerTestFileUrl}/test2.html';
} else {
// serverUrl =
// 'http://${state.ip}:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
serverUrl =
'http://${state.ip}:${_server.port}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
'${Constant.localServerUrl}/index.html#/h5/login/pages/applogin?sessionCode=${state.sessionCode}&userCode=${state.userCode}&classCode=${state.classCode}&userType=${state.userType}&stuId=${state.stuId}';
}
serverUrl = '${Constant.localServerUrl}/index.html';
......@@ -275,6 +281,14 @@ class WebCubit extends Cubit<WebState> {
router.go('/wechatAuth');
}
void goLogin() {
router.go('/loginMain');
}
void goMqtt(){
router.go('/mqtt');
}
//测试
void goAuth() {
// String serverUrl = 'http://${state.ip}:${_server.port}/index.html';
......@@ -749,7 +763,6 @@ class WebCubit extends Cubit<WebState> {
try {
await _recorder?.closeRecorder();
} catch (e) {
// ignore: empty_catches
print(e);
}
emit(state.copyWith(recorderIsInit: false, recordState: 0, recordPath: ''));
......@@ -851,7 +864,6 @@ class WebCubit extends Cubit<WebState> {
try {
await _player?.closePlayer();
} catch (e) {
// ignore: empty_catches
print(e);
}
emit(state.copyWith(playerIsInit: false, playState: 0, playId: ''));
......@@ -868,14 +880,12 @@ class WebCubit extends Cubit<WebState> {
try {
await _recorder?.closeRecorder();
} catch (e) {
// ignore: empty_catches
print(e);
}
try {
await _player?.closePlayer();
} catch (e) {
// ignore: empty_catches
print(e);
}
......
......@@ -12,7 +12,7 @@ class Constant {
static const int obsUploadChunkSize = 1024 * 1024 * 5;
/// 测试阶段使用的 h5 服务地址
static const String h5Server = 'appdev-xj.banxiaoer.net';
// static const String h5Server = 'appdev-th.banxiaoer.net';
// static const String h5Server = 'appdev-xj.banxiaoer.net';
static const String h5Server = 'appdev-th.banxiaoer.net';
// static const String h5Server = '192.168.1.136';
}
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
late final Database bxeDb;
Future<void> initDatabase() async {
// 获取数据库存储的默认路径
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'bxe_database.db');
// 打开数据库,如果不存在则创建
Database database = await openDatabase(
path,
version: 1, // 数据库版本
onCreate: (Database db, int version) async {
// 创建表
await db.execute(
// 'CREATE TABLE msg(id INTEGER PRIMARY KEY, content TEXT)',
'CREATE TABLE msg(id INTEGER, content TEXT)',
);
},
);
bxeDb = database;
}
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/services.dart';
late HttpServer localServer;
Future<void> startLocalServer() async {
HttpServer localServer = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
// print('本地服务器启动在端口: ${localServer.port}');
localServer.listen((HttpRequest request) async {
final String requestPath = request.uri.path == '/' ? '/index.html' : request.uri.path;
try {
if (requestPath.startsWith('/temp/')) {
// 临时目录文件的请求
await _serveTempFile(request, requestPath);
} else if (requestPath.startsWith('/test/')) {
// assets文件服务逻辑
await _serveAssetFile(request, requestPath);
} else {
// asset/dist.zip 文件服务逻辑
await _serveZipFileContent(request, requestPath);
}
} catch (e) {
print('处理请求时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Internal server error')
..close();
}
});
}
// 临时目录的文件
Future<void> _serveTempFile(HttpRequest request, String requestPath) async {
try {
// 临时文件已经设备路径
// 构建文件路径(移除 /temp 前缀)
final String filePath = requestPath.substring('/temp/'.length);
// 检查文件是否存在
final File file = File(filePath);
if (await file.exists()) {
// 读取文件内容
final List<int> bytes = await file.readAsBytes();
request.response
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
} else {
request.response
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
} catch (e) {
print('读取临时文件时出错: $e');
request.response
..statusCode = HttpStatus.notFound
..write('File not found')
..close();
}
}
Future<void> _serveZipFileContent(HttpRequest request, String requestPath) async {
String zipAssetPath = 'assets/dist.zip';
try {
// 使用 rootBundle.load 加载资源文件
final ByteData data = await rootBundle.load(zipAssetPath);
final List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// 读取并解压zip文件内容
final Archive archive = ZipDecoder().decodeBytes(bytes);
// 查找请求的内部文件
ArchiveFile? targetFile;
for (final file in archive) {
// 标准化路径分隔符(统一使用 '/')
String zipFileName = file.name.replaceAll('\\', '/');
// 移除开头的 '/'(如果存在)
if (requestPath.startsWith('/')) {
requestPath = requestPath.substring(1);
}
if (zipFileName == requestPath) {
targetFile = file;
break;
}
}
if (targetFile == null) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found in zip: $requestPath')
..close();
return;
}
// 返回文件内容
request.response
..headers.contentType = ContentType.parse(_getContentType(requestPath))
..add(targetFile.content as List<int>)
..close();
} catch (e) {
print('读取zip文件时出错: $e');
request.response
..statusCode = HttpStatus.internalServerError
..write('Error reading zip file')
..close();
}
}
// 访问assets目录下的文件
Future<void> _serveAssetFile(HttpRequest request, String requestPath) async {
// 构建文件路径(移除 /test 前缀)
final String path = requestPath.substring('/test'.length);
final String filePath = 'assets$path';
try {
final ByteData data = await rootBundle.load(filePath);
final List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
request.response
..headers.contentType = ContentType.parse(_getContentType(filePath))
..add(bytes)
..close();
} catch (e) {
request.response
..statusCode = HttpStatus.notFound
..write('File not found: $filePath')
..close();
}
}
String _getContentType(String filePath) {
if (filePath.endsWith('.html')) return 'text/html';
if (filePath.endsWith('.js')) return 'application/javascript';
if (filePath.endsWith('.css')) return 'text/css';
if (filePath.endsWith('.png')) return 'image/png';
if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) return 'image/jpeg';
return 'application/octet-stream';
}
......@@ -172,6 +172,7 @@ Future<void> setupLocator() async {
/// service
getIt.registerLazySingleton<ApiService>(() => ApiService(baseUrl: 'https://dev.banxiaoer.net'));
/// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
}
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/mqtt_service.dart';
/// 此处暂时测试
/// 正常需要在登录状态下,查询host和jwt
Future<void> registerMqtt() async {
String mqttHost = '58.87.99.45';
int mqttPort = 1883;
String mqttClientId = 'asdfasdf';
int keepAlive = 60;
String mqttUsername = 'user';
String mqttPassword =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjIzOTY5MDQsInJzIjoib2sifQ.tsouW-uHTe6ndiEqSE4wcR_oSScuoS0qVvqWtNSaXgWkX2yEja9ImnMYr6jNKxJY8_qox9UdXRuCept5cTM4-8ZaA0BBlYXwr3LbC3yPmniFhs-tAMFbZqG2-3r0sc5NMbE3M1fXMi3dmvQc2AlyazheL98EmRNILFsz6pZ-x5rR1gFulZ53PXa7fX60XRlXg8jc3-89gdUJcS0MOMU7yyU0Sv3QBBplLr3CmWCoYX99a_3QHvq3o7aUTJ_Ed5Ms9_3k4QHgawsfdTWjZkyBouAvLMgVkt4D0PJbhgsjzAaA01Q7jEJ_SokNojOFfQYuHNScczmlOLXocJCCD4189A';
/// 初始化MQTT客户端
var mqttService = MqttService(mqttHost, mqttPort, mqttClientId, keepAlive, mqttUsername, mqttPassword);
await mqttService.initConn();
/// 设置到getIt,用于获取使用
getIt.registerSingleton(mqttService);
}
import 'package:appframe/ui/pages/adv_page.dart';
import 'package:appframe/ui/pages/link_page.dart';
import 'package:appframe/ui/pages/login_main_page.dart';
import 'package:appframe/ui/pages/login_phone_page.dart';
import 'package:appframe/ui/pages/mqtt_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart';
import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/pages/wechat_auth_page.dart';
......@@ -6,7 +10,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GoRouter router = GoRouter(
initialLocation: '/web',
initialLocation: '/adv',
routes: <RouteBase>[
GoRoute(
path: '/web',
......@@ -32,5 +36,29 @@ final GoRouter router = GoRouter(
return const LinkPage();
},
),
GoRoute(
path: '/loginMain',
builder: (BuildContext context, GoRouterState state) {
return const LoginMainPage();
},
),
GoRoute(
path: '/loginPhone',
builder: (BuildContext context, GoRouterState state) {
return const LoginPhonePage();
},
),
GoRoute(
path: '/adv',
builder: (BuildContext context, GoRouterState state) {
return const AdvPage();
},
),
GoRoute(
path: '/mqtt',
builder: (BuildContext context, GoRouterState state) {
return const MqttPage();
},
),
],
);
import 'package:fluwx/fluwx.dart';
import 'dart:io' show Platform;
final Fluwx fluwx = Fluwx();
Future<void> registerWechatApi() async {
if (Platform.isAndroid || Platform.isIOS) {
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://dev.banxiaoer.net/path/to/wechat/");
} else {
print("✅ Fluwx is not supported on this platform (${Platform.operatingSystem}). Skip registerApi().");
}
}
......@@ -15,6 +15,7 @@ class AudioPlayHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
......@@ -23,6 +24,9 @@ class AudioPlayHandler extends MessageHandler {
bool result = await _webCubit!.playAudio(url);
return result;
} finally {
_unfollowCubit();
}
}
}
......
......@@ -15,8 +15,12 @@ class AudioRecorderStartHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
bool result = await _webCubit!.startRecording();
return result;
} finally {
_unfollowCubit();
}
}
}
......@@ -34,8 +38,12 @@ class AudioRecorderPauseHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
bool result = await _webCubit!.pauseRecording();
return result;
} finally {
_unfollowCubit();
}
}
}
......@@ -53,8 +61,12 @@ class AudioRecorderResumeHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
bool result = await _webCubit!.resumeRecording();
return result;
} finally {
_unfollowCubit();
}
}
}
......@@ -72,7 +84,11 @@ class AudioRecorderStopHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
return await _webCubit!.stopRecording();
} finally {
_unfollowCubit();
}
}
}
......@@ -90,6 +106,10 @@ class AudioRecorderClearHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
return await _webCubit!.clearRecording();
} finally {
_unfollowCubit();
}
}
}
......@@ -21,6 +21,7 @@ class ChooseImageHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
......@@ -46,5 +47,8 @@ class ChooseImageHandler extends MessageHandler {
}
_webCubit!.setChooseImageCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
......@@ -21,6 +21,7 @@ class ChooseVideoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
......@@ -46,134 +47,8 @@ class ChooseVideoHandler extends MessageHandler {
}
_webCubit!.setChooseVideoCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
// class ChooseVideoHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(params) async {
// if (params is! Map<String, dynamic>) {
// throw Exception('参数错误');
// }
// var sourceType = params['sourceType'] as String;
// if (sourceType != 'album' && sourceType != 'camera') {
// throw Exception('参数错误');
// }
// // 暂时忽略对此参数的处理
// List<dynamic>? sizeType;
// if (params.containsKey('sizeType')) {
// sizeType = params['sizeType'] as List<dynamic>;
// if (sizeType.isEmpty || sizeType.length > 2) {
// throw Exception('参数错误');
// }
// }
//
// int count = 1;
// if (params.containsKey('count')) {
// count = params['count'] as int;
// if (count < 1 || count > 9) {
// throw Exception('参数错误');
// }
// }
//
// int number = 60;
// if (params.containsKey('number')) {
// number = params['number'] as int;
// if (number < 1 || number > 60) {
// throw Exception('参数错误');
// }
// }
//
// // 从相册选择
// if (sourceType == 'album') {
// if (count == 1) {
// return await _selectSingleVideo();
// } else {
// return await _selectMultiVideo(count);
// }
// }
// // 拍摄
// else {
// return await _cameraSingle();
// }
// }
//
// Future<List<dynamic>> _selectSingleVideo() async {
// final ImagePicker picker = ImagePicker();
// final XFile? pickedVideo = await picker.pickVideo(source: ImageSource.gallery);
//
// // 用户取消选择,返回空数组
// if (pickedVideo == null) {
// return [];
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// return [await _handleOne(pickedVideo, tempDir)];
// }
//
// Future<List<dynamic>> _selectMultiVideo(int limit) async {
// final ImagePicker picker = ImagePicker();
// final List<XFile> pickedVideoList = await picker.pickMultiVideo(limit: limit);
//
// // 用户取消选择,返回空数组
// if (pickedVideoList.isEmpty) {
// return [];
// }
//
// // 限制最多limit张
// if (pickedVideoList.length > limit) {
// pickedVideoList.removeRange(limit, pickedVideoList.length);
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// final List<Map<String, dynamic>> result = [];
// for (final XFile? file in pickedVideoList) {
// if (file != null) {
// result.add(await _handleOne(file, tempDir));
// }
// }
// return result;
// }
//
// ///
// /// 拍照
// ///
// Future<List<Map<String, dynamic>>?> _cameraSingle() async {
// final ImagePicker picker = ImagePicker();
//
// final XFile? pickedFile = await picker.pickVideo(source: ImageSource.camera);
//
// // 用户取消选择,返回空数组
// if (pickedFile == null) {
// return [];
// }
//
// // 获取临时目录
// final Directory tempDir = await getTemporaryDirectory();
//
// return [await _handleOne(pickedFile, tempDir)];
// }
//
// Future<Map<String, dynamic>> _handleOne(XFile pickedFile, Directory tempDir) async {
// var sourceFile = File(pickedFile.path);
//
// final thumbnailPath = await ThumbnailUtil.genVideoThumbnail(pickedFile.path, tempDir);
// // 暂时这样进行接口测试
// // 根据视频预览图,获取视频的宽度和高度
// final sizeResult = ImageSizeGetter.getSizeResult(FileInput(File(thumbnailPath!)));
//
// // 返回一个元素的数组
// return {
// "tempFilePath": "/temp${sourceFile.path}",
// "size": sourceFile.lengthSync(),
// "width": sizeResult.size.width,
// "height": sizeResult.size.height,
// "thumbTempFilePath": '/temp$thumbnailPath',
// "fileType": sourceFile.path.split('/').last.split('.').last,
// };
// }
// }
......@@ -15,7 +15,11 @@ class GoLoginHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
_webCubit!.goWechatAuth();
try {
_webCubit!.goLogin();
return;
} finally {
_unfollowCubit();
}
}
}
......@@ -21,6 +21,10 @@ class OrientationHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
try {
_webCubit!.setOrientationCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
import 'dart:ffi';
import 'package:appframe/services/dispatcher.dart';
import 'package:flutter_sound/flutter_sound.dart';
FlutterSoundPlayer? localPlayer;
bool isLocalPlaying = false;
bool isLocalPausePlaying = false;
String? localPlayedFilePath;
Future<bool> initLocalPlayer() async {
localPlayer = FlutterSoundPlayer();
try {
await localPlayer!.openPlayer();
} catch (e) {
print(e);
await _resetPlayerStatus();
throw Exception('打开播放器失败!');
}
return true;
}
Future<bool> startLocalPlayer(String url) async {
if (localPlayer == null) {
await initLocalPlayer();
}
try {
localPlayedFilePath = url;
localPlayer!.startPlayer(fromURI: url);
return true;
} catch (e) {
print(e);
await _resetPlayerStatus();
return false;
}
}
Future<bool> pauseLocalPlayer() async {
if(!(isLocalPlaying==true && isLocalPausePlaying==false)) {
return false;
}
try {
await localPlayer!.pausePlayer();
return true;
} catch (e) {
print(e);
await _resetPlayerStatus();
return false;
}
}
Future<bool> resumeLocalPlayer() async {
if(!(isLocalPlaying==true && isLocalPausePlaying==true)) {
return false;
}
try {
await localPlayer!.resumePlayer();
return true;
} catch (e) {
print(e);
await _resetPlayerStatus();
return false;
}
}
Future<bool> stopLocalPlayer() async {
try {
localPlayer!.stopPlayer();
return true;
} catch (e) {
print(e);
return false;
} finally {
await _resetPlayerStatus();
}
}
void closeLocalPlayer() async {
await _resetPlayerStatus();
}
Future<void> _resetPlayerStatus() async {
try {
await localPlayer?.closePlayer();
} catch (e) {
print(e);
}
localPlayer = null;
isLocalPlaying = false;
isLocalPausePlaying = false;
localPlayedFilePath = null;
}
// class AudioPlayHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await startLocalPlayer(params['url']);
// return result;
// }
// }
//
// class AudioPauseHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await pauseLocalPlayer();
// return result;
// }
// }
//
// class AudioResumeHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await resumeLocalPlayer();
// return result;
// }
// }
//
// class AudioStopHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await stopLocalPlayer();
// return result;
// }
// }
//
// class AudioClearHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// closeLocalPlayer();
// return true;
// }
// }
import 'package:appframe/services/dispatcher.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
/*录音相关全局参数*/
FlutterSoundRecorder? localRecorder;
bool isLocalRecording = false;
bool isLocalPauseRecording = false;
String? localRecordedFilePath;
Future<bool> initLocalRecorder() async {
// 请求麦克风权限
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('no auth');
}
if (!(!isLocalRecording && !isLocalPauseRecording && localRecordedFilePath == null)) {
return false;
}
// 打开录音器
FlutterSoundRecorder recorder = FlutterSoundRecorder();
try {
localRecorder = await recorder.openRecorder();
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
throw Exception('打开录音器失败!');
}
}
Future<bool> startLocalRecording() async {
if (isLocalRecording) {
return false;
}
if (!await initLocalRecorder()) {
return false;
}
try {
// 获取应用文档目录路径
final directory = await getApplicationDocumentsDirectory();
String filePath = '${directory.path}/bxe_recording.aac';
await localRecorder!.startRecorder(toFile: filePath, codec: Codec.aacMP4);
isLocalRecording = true;
isLocalPauseRecording = false;
localRecordedFilePath = filePath;
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
return false;
}
}
Future<bool> pauseLocalRecording() async {
if (!(isLocalRecording && !isLocalPauseRecording)) {
return false;
}
try {
await localRecorder!.pauseRecorder();
isLocalPauseRecording = true;
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
return false;
}
}
Future<bool> resumeLocalRecording() async {
if (!(isLocalRecording && isLocalPauseRecording)) {
return false;
}
try {
await localRecorder!.resumeRecorder();
isLocalPauseRecording = false;
return true;
} catch (e) {
print(e);
await _resetRecorderStatus();
return false;
}
}
Future<String?> stopLocalRecording() async {
if (!(localRecorder != null && isLocalRecording)) {
throw Exception("录音器未初始化");
}
try {
String? url = await localRecorder!.stopRecorder();
isLocalRecording = false;
isLocalPauseRecording = false;
if (url == null || url == "") {
throw Exception("录音失败");
}
await _resetRecorderStatus();
return url;
} catch (e) {
print(e);
await _resetRecorderStatus();
throw Exception("录音失败");
}
}
/// 关闭录音器,释放资源
void closeLocalRecorder() async {
await _resetRecorderStatus();
}
Future<void> _resetRecorderStatus() async {
try {
await localRecorder?.closeRecorder();
} catch (e) {
print(e);
}
localRecorder == null;
isLocalRecording = false;
isLocalPauseRecording = false;
localRecordedFilePath = null;
}
// class AudioRecorderStartHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await startLocalRecording();
// return result;
// }
// }
//
// class AudioRecorderPauseHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await pauseLocalRecording();
// return result;
// }
// }
//
// class AudioRecorderResumeHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// bool result = await resumeLocalRecording();
// return result;
// }
// }
//
// class AudioRecorderStopHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// return await stopLocalRecording();
// }
// }
//
// class AudioRecorderClearHandler extends MessageHandler {
// @override
// Future<dynamic> handleMessage(dynamic params) async {
// closeLocalRecorder();
// return true;
// }
// }
import 'dart:async';
import 'package:appframe/services/dispatcher.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:flutter/material.dart';
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/services/dispatcher.dart';
class ScanCodeHandler extends MessageHandler {
late WebCubit? _webCubit;
......
......@@ -15,6 +15,7 @@ class SetTitleHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
......@@ -23,5 +24,8 @@ class SetTitleHandler extends MessageHandler {
final bool showBack = params['showBack'] as bool;
return _webCubit!.setTitle(title, showBack);
} finally {
_unfollowCubit();
}
}
}
......@@ -16,7 +16,14 @@ class UpgradeHandler extends MessageHandler {
final version = params['version'] as String;
// 1 下载
var direct = await getExternalStorageDirectory();
Directory? direct;
if (Platform.isAndroid) {
direct = await getExternalStorageDirectory();
} else if (Platform.isIOS || Platform.isMacOS) {
direct = await getApplicationDocumentsDirectory();
} else {
throw Exception('未知平台');
}
var saveFilePath = '${direct?.path}/dist_$version.zip';
var dio = Dio();
......
......@@ -21,6 +21,10 @@ class WindowInfoHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
try {
_webCubit!.setWindowInfoCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
import 'package:appframe/config/db.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/wechat.dart';
import 'package:appframe/config/mqtt.dart';
import 'package:flutter/material.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await registerWechatApi();
await setupLocator();
await registerMqtt();
await initDatabase();
runApp(const App());
}
......@@ -144,10 +144,13 @@ class LocalServerService {
}
Future<void> _initHttpDirectory() async {
if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory();
_httpDirectory = '${direct?.path}/http_dist_assets';
// var direct = await getApplicationSupportDirectory();
// _httpDirectory = '${direct.path}/http_dist_assets';
} else if (Platform.isIOS || Platform.isMacOS) {
var direct = await getApplicationSupportDirectory();
_httpDirectory = '${direct.path}/http_dist_assets';
}
}
Future<void> _extractDist() async {
......@@ -190,8 +193,7 @@ class LocalServerService {
var distUrl = "https://github.com/xinxin-wu/flutter_web_dist/releases/download/v1.0.0/dist.zip";
// Dio进行下载
var dio = Dio();
dio
.download(
dio.download(
distUrl,
'$httpDirectory/dist.zip',
onReceiveProgress: (received, total) {
......@@ -199,8 +201,7 @@ class LocalServerService {
print((received / total * 100).toStringAsFixed(0) + "%");
}
},
)
.then((_) {
).then((_) {
_extractDist();
});
dio.close();
......
import 'dart:async';
import 'dart:io';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
class MqttService {
/// MQTT服务器信息
final String _host;
final int _port;
final String _clientId;
final int _keepAlive;
final String _username;
/// 重连的时候要重新获取一下 token
final String _password;
/// MQTT客户端
late MqttServerClient _client;
StreamSubscription? _subscription;
/// MQTT重连相关变量
int _reconnectAttempts = 0;
final int _maxReconnectAttempts = 5;
Timer? _reconnectTimer;
MqttService(this._host, this._port, this._clientId, this._keepAlive, this._username, this._password);
Future<void> initConn() async {
_client = MqttServerClient(_host, _clientId);
_client.logging(on: false);
_client.port = _port;
_client.keepAlivePeriod = _keepAlive;
_client.onDisconnected = onDisconnected;
_client.onConnected = onConnected;
_client.onSubscribed = onSubscribed;
_client.pongCallback = pong;
final connMess = MqttConnectMessage()
.withWillTopic('willtopic')
.withWillMessage('My Will message')
.startClean()
.withWillQos(MqttQos.atLeastOnce);
_client.connectionMessage = connMess;
try {
print('MQTT 开始连接......');
await _client.connect(_username, _password);
} on NoConnectionException catch (e) {
print('MQTT客户端连接失败: $e');
_client.disconnect();
} on SocketException catch (e) {
print('MQTT客户端连接失败: $e');
_client.disconnect();
}
// 订阅主题
subscribe("\$q/bxe/abc");
// 监听消息
listen(onMessageReceived);
}
void onConnected() {
print('MQTT客户端连接成功');
_reconnectAttempts = 0;
_reconnectTimer?.cancel();
}
void onDisconnected() {
print('MQTT客户端断开连接');
// if (_reconnectAttempts < _maxReconnectAttempts) {
// handleReconnect(_host, _port, _clientId, '');
// }
}
void onSubscribed(String topic) {
print('MQTT客户端订阅主题: $topic');
}
void onMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('MQTT消息接收: topic is ${c[0].topic}, payload is $pt');
// 处理接收到的消息
}
void subscribe(String topic) {
_client.subscribe(topic, MqttQos.atLeastOnce);
}
void listen(Function(List<MqttReceivedMessage<MqttMessage?>>? c) onMessageReceivedCallback) {
_subscription?.cancel();
_subscription = _client.updates?.listen(onMessageReceivedCallback);
}
void disconnect() {
_client.disconnect();
}
bool isConnected() {
return _client.connectionStatus?.state == MqttConnectionState.connected;
}
void pong() {
print('Ping response client callback invoked');
}
void onMqttMessageReceived(List<MqttReceivedMessage<MqttMessage?>>? c) {
final recMess = c![0].payload as MqttPublishMessage;
final pt = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
print('MQTT消息接收: topic is ${c[0].topic}, payload is $pt');
// 处理接收到的消息
}
void handleReconnect(String host, int port, String username, String password) {
if (_reconnectAttempts >= _maxReconnectAttempts) {
print('已达到最大重连次数,停止重连');
return;
}
_reconnectAttempts++;
print('第 $_reconnectAttempts 次重连尝试,${_maxReconnectAttempts - _reconnectAttempts} 次机会剩余');
// 取消之前的重连定时器(如果有)
_reconnectTimer?.cancel();
// 设置延迟重连,每次重连间隔递增
final delay = Duration(seconds: _reconnectAttempts * 5);
_reconnectTimer = Timer(delay, () async {
print('正在尝试重新连接...');
await initConn();
if (!isConnected()) {
disconnect();
}
});
}
}
import 'package:appframe/bloc/adv_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AdvPage extends StatelessWidget {
const AdvPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AdvCubit(),
child: BlocConsumer<AdvCubit, AdvState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text(''),
),
// body: Center(
// child: Text(
// '${state.countdown}',
// style: TextStyle(fontSize: 50),
// ),
// ),
body: Stack(children: [
Center(
child: GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('点击了开屏广告')));
},
child: Text(
'开屏广告',
style: TextStyle(fontSize: 50),
)),
),
Positioned(
bottom: 30,
right: 30,
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFF7691FA),
),
child: Center(
child: Text(
'${state.countdown}',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
),
),
]),
);
},
listener: (context, state) {}),
);
}
}
import 'package:appframe/bloc/login_main_cubit.dart';
import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:appframe/ui/widgets/login/login_page_header_widget.dart';
import 'package:appframe/ui/widgets/login/login_page_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class LoginMainPage extends StatelessWidget {
const LoginMainPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginMainCubit(LoginMainState()),
child: BlocConsumer<LoginMainCubit, LoginMainState>(builder: (context, state) {
var loginMainCubit = context.read<LoginMainCubit>();
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
children: [
LoginPageImageWidget(),
Transform.translate(
offset: Offset(0, -40), // 向上移动40像素
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
color: Colors.white, // 设置背景色
),
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 30),
// _buildHeader(),
LoginPageHeaderWidget(),
SizedBox(height: 25),
_buildLoginButtons(context, loginMainCubit, state.agreed),
SizedBox(height: 20),
_buildAgreement(context, loginMainCubit, state.agreed),
],
),
),
),
],
)),
),
);
}, listener: (context, state) {
if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginMainCubit>());
}
}),
);
}
Widget _buildHeader() {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'欢迎登录',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 6),
Text(
'班小二,班级群必备效率工具',
style: TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
],
);
}
Widget _buildLoginButtons(BuildContext context, LoginMainCubit loginMainCubit, bool agreed) {
return Column(
children: [
SizedBox(
width: double.infinity,
height: 47,
child: ElevatedButton(
onPressed: () {
loginMainCubit.wechatAuth();
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF00CB60),
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(23.5),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/login/wechat_white_icon.png'),
SizedBox(width: 6),
Text('微信登录'),
],
),
),
),
SizedBox(height: 15),
SizedBox(
width: double.infinity,
height: 47,
child: ElevatedButton(
onPressed: () {
loginMainCubit.goLoginPhone();
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF7691FA),
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(23.5),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/login/phone_white_icon.png'),
// SizedBox(width: 8),
Text('手机号登录'),
],
),
),
),
],
);
}
Widget _buildAgreement(BuildContext context, LoginMainCubit loginMainCubit, bool agreed) {
return Padding(
padding: const EdgeInsets.only(bottom: 32.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Theme(
data: Theme.of(context).copyWith(
checkboxTheme: CheckboxThemeData(
shape: CircleBorder(
side: BorderSide(width: 0.5, color: Color(0xFF999999)),
),
fillColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return Color(0xFF7691FA); // 选中时的颜色
}
return Colors.white; // 未选中时的颜色
},
),
checkColor: WidgetStateProperty.all<Color>(Colors.white),
side: BorderSide(width: 0.5, color: Color(0xFF999999)),
),
cardColor: Colors.white,
unselectedWidgetColor: Color(0xFF999999),
),
child: Checkbox(
value: agreed,
onChanged: (bool? value) {
loginMainCubit.toggleAgreed(value!);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, // 缩小点击区域,减小间隔
),
),
LoginPageAgreedWidget(),
],
),
);
}
Future<void> _showAgreementDialog(BuildContext context, LoginMainCubit loginMainCubit) async {
final result = await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'个人信息保护指引',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
// fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
content: Text.rich(
TextSpan(
children: [
TextSpan(
text: '感谢使用班小二APP,为保护您的个人权益,请仔细阅读并充分理解',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《班小二数据据安全和隐私政策》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
),
TextSpan(
text: '与',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《用户协议》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
),
TextSpan(
text: '。如您同意上述文件的全部内容,请点击“同意”以继续。',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
],
),
),
actions: [
Table(
children: [
TableRow(
children: [
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF666666),
textStyle: TextStyle(fontSize: 17),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('放弃登录'),
),
),
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop('OK');
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('同意'),
),
),
],
),
],
),
],
);
},
);
if (result != null) {
loginMainCubit.confirmAgreed();
} else {
loginMainCubit.cancelAgreed();
}
}
}
import 'package:appframe/bloc/mqtt_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MqttPage extends StatelessWidget {
const MqttPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => MqttCubit(),
child: BlocConsumer<MqttCubit, MqttState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: Text('推送测试'),
centerTitle: true,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
context.read<MqttCubit>().goWeb();
},
),
),
body: ListView(
children: state.messages
.map((message) => ListTile(
title: Text(message),
))
.toList(),
));
},
listener: (context, state) {},
));
}
}
......@@ -62,24 +62,112 @@ class WebPage extends StatelessWidget {
},
)
: null,
actions: [
Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return SizedBox(
height: 300,
child: Column(
children: [
// actions: [
// Builder(
// builder: (BuildContext context) {
// return IconButton(
// icon: const Icon(Icons.more_vert),
// onPressed: () {
// showModalBottomSheet(
// 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(
width: MediaQuery.of(ctx).size.width * 0.8,
child: ListView(padding: EdgeInsets.zero, children: [
// DrawerHeader(
// decoration: BoxDecoration(
// color: Theme.of(ctx).colorScheme.primary,
// ),
// child: Text('设置',
// style: TextStyle(
// color: Theme.of(ctx).colorScheme.onPrimary,
// fontSize: 24,
// ))),
ListTile(
tileColor: Color(0xFF7691FA),
title: Text('设置',
style: TextStyle(
color: Theme.of(ctx).colorScheme.onPrimary,
fontSize: 24,
)
),
),
ListTile(
leading: const Icon(Icons.chat_bubble_outline),
title: const Text('推送测试'),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().goMqtt();
},
),
ListTile(
leading: const Icon(Icons.login),
title: const Text('登录界面'),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().goLogin();
},
),
ListTile(
leading: const Icon(Icons.chat_outlined),
title: const Text('微信授权'),
onTap: () {
Navigator.pop(context);
Navigator.pop(ctx);
ctx.read<WebCubit>().goWechatAuth();
},
),
......@@ -87,7 +175,7 @@ class WebPage extends StatelessWidget {
leading: const Icon(Icons.accessibility_new),
title: const Text('身份认证'),
onTap: () {
Navigator.pop(context);
Navigator.pop(ctx);
ctx.read<WebCubit>().goAuth();
},
),
......@@ -95,7 +183,7 @@ class WebPage extends StatelessWidget {
leading: const Icon(Icons.app_blocking_sharp),
title: const Text('打开小程序'),
onTap: () {
Navigator.pop(context);
Navigator.pop(ctx);
ctx.read<WebCubit>().goMiniProgram();
},
),
......@@ -103,7 +191,7 @@ class WebPage extends StatelessWidget {
leading: const Icon(Icons.refresh),
title: const Text('刷新'),
onTap: () {
Navigator.pop(context);
Navigator.pop(ctx);
ctx.read<WebCubit>().refresh();
},
),
......@@ -111,21 +199,11 @@ class WebPage extends StatelessWidget {
leading: const Icon(Icons.cleaning_services),
title: const Text('清理缓存'),
onTap: () {
Navigator.pop(context);
Navigator.pop(ctx);
ctx.read<WebCubit>().clearStorage();
},
),
],
),
);
},
);
},
);
},
),
],
),
])),
body: state.loaded
? SizedBox(
height: MediaQuery.of(ctx).size.height - 120, // 减去100像素留空
......@@ -150,3 +228,19 @@ 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 'package:flutter/material.dart';
class LoginPageAgreedWidget extends StatelessWidget {
const LoginPageAgreedWidget({super.key});
@override
Widget build(BuildContext context) {
return Text.rich(
TextSpan(
children: [
TextSpan(
text: '同意',
style: TextStyle(color: Color(0xFF999999), fontSize: 14),
),
TextSpan(
text: '《隐私保障》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
),
TextSpan(
text: '和',
style: TextStyle(color: Color(0xFF999999), fontSize: 14),
),
TextSpan(
text: '《用户协议》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
),
],
),
);
}
}
import 'package:flutter/material.dart';
class LoginPageHeaderWidget extends StatelessWidget {
const LoginPageHeaderWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'欢迎登录',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color(0xFF000000),
),
),
SizedBox(height: 6),
Text(
'班小二,班级群必备效率工具',
style: TextStyle(
fontSize: 14,
color: Color(0xFF000000),
),
),
],
);
}
}
import 'package:flutter/material.dart';
class LoginPageImageWidget extends StatelessWidget {
const LoginPageImageWidget({super.key});
@override
Widget build(BuildContext context) {
return Image.asset(
'assets/images/login/login_bg.png',
width: double.infinity,
fit: BoxFit.cover,
);
}
}
......@@ -19,6 +19,7 @@ import package_info_plus
import path_provider_foundation
import photo_manager
import shared_preferences_foundation
import sqflite_darwin
import url_launcher_macos
import video_compress
import video_player_avfoundation
......@@ -39,6 +40,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
......
......@@ -36,6 +36,9 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- video_compress (0.3.0):
......@@ -63,6 +66,7 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- photo_manager (from `Flutter/ephemeral/.symlinks/plugins/photo_manager/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- video_compress (from `Flutter/ephemeral/.symlinks/plugins/video_compress/macos`)
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
......@@ -99,6 +103,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/photo_manager/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
video_compress:
......@@ -124,6 +130,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
video_compress: 752b161da855df2492dd1a8fa899743cc8fe9534
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
......
......@@ -62,12 +62,14 @@ dependencies:
json_annotation: ^4.9.0
mime: ^2.0.0
mobile_scanner: ^7.0.1
mqtt_client: ^10.11.1
network_info_plus: ^7.0.0
open_file: ^3.5.10
path: ^1.9.1
path_provider: ^2.1.5
permission_handler: ^12.0.1
shared_preferences: ^2.5.3
sqflite: ^2.4.2
# video_thumbnail: ^0.5.6
url_launcher: ^6.3.2
uuid: ^4.5.1
......@@ -113,6 +115,7 @@ flutter:
assets:
- assets/
- assets/dist.zip
- assets/images/login/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!