Commit e53ceeae by Administrator

Merge branch 'develop251027' into develop

内容合并
2 parents ba18c03b efd1f3ba
Showing 86 changed files with 1711 additions and 756 deletions
......@@ -43,3 +43,5 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
assets/static
pubspec.lock
......@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "05db9689081f091050f01aed79f04dce0c750154"
revision: "fcf2c11572af6f390246c056bc905eca609533a0"
channel: "stable"
project_type: app
......@@ -13,14 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
- platform: android
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
- platform: ios
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
- platform: linux
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
- platform: macos
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
- platform: web
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
- platform: windows
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
# User provided section
......
✅ 执行安装:
cd macos
pod install --repo-update
✅ 返回项目根目录,重新运行 Flutter:
flutter clean
flutter pub get
flutter run -d macos
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
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
#platform :ios, '12.0'
#source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
#platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
......@@ -25,11 +26,14 @@ end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
pod 'SDWebImage', :git => 'https://gitee.com/mirrors/SDWebImage.git', :branch => '5.21.3'
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
# pod 'SDWebImage', :modular_headers => true
# pod 'SDWebImageWebPCoder', :modular_headers => true
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
......
PODS:
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
- flutter_image_compress_common (1.0.0):
- Flutter
- Mantle
- SDWebImage
- SDWebImageWebPCoder
- flutter_sound (9.28.0):
- Flutter
- flutter_sound_core (= 9.28.0)
- flutter_sound_core (9.28.0)
- fluwx (0.0.1):
- Flutter
- fluwx/pay (= 0.0.1)
- fluwx/pay (0.0.1):
- Flutter
- WechatOpenSDK-XCFramework (~> 2.0.5)
- gallery_saver_plus (0.0.1):
- Flutter
- image_picker_ios (0.0.1):
- Flutter
- libwebp (1.5.0):
- libwebp/demux (= 1.5.0)
- libwebp/mux (= 1.5.0)
- libwebp/sharpyuv (= 1.5.0)
- libwebp/webp (= 1.5.0)
- libwebp/demux (1.5.0):
- libwebp/webp
- libwebp/mux (1.5.0):
- libwebp/demux
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- SDWebImage (5.21.3):
- SDWebImage/Core (= 5.21.3)
- SDWebImage/Core (5.21.3)
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
- video_thumbnail (0.0.1):
- Flutter
- libwebp
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
- WechatOpenSDK-XCFramework (2.0.5)
DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
- flutter_sound (from `.symlinks/plugins/flutter_sound/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- gallery_saver_plus (from `.symlinks/plugins/gallery_saver_plus/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- SDWebImage (from `https://gitee.com/mirrors/SDWebImage.git`, branch `5.21.3`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- flutter_sound_core
- libwebp
- Mantle
- SDWebImageWebPCoder
- SwiftyGif
- WechatOpenSDK-XCFramework
EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_image_compress_common:
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
flutter_sound:
:path: ".symlinks/plugins/flutter_sound/ios"
fluwx:
:path: ".symlinks/plugins/fluwx/ios"
gallery_saver_plus:
:path: ".symlinks/plugins/gallery_saver_plus/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
SDWebImage:
:branch: 5.21.3
:git: https://gitee.com/mirrors/SDWebImage.git
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
video_thumbnail:
:path: ".symlinks/plugins/video_thumbnail/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
CHECKOUT OPTIONS:
SDWebImage:
:commit: 7312c9ce5e1987cb6cbf11213b5fc1fc03abe3a8
:git: https://gitee.com/mirrors/SDWebImage.git
SPEC CHECKSUMS:
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_sound: b9236a5875299aaa4cef1690afd2f01d52a3f890
flutter_sound_core: 427465f72d07ab8c3edbe8ffdde709ddacd3763c
fluwx: 620a35d7ba14842304dc2b19fed6b4e279da9d0f
gallery_saver_plus: 61c8a831d5975e2bf57f87bcd64379dcc7d29ff2
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
SDWebImage: 22f3e17ef402a1cabed97a37a36397ec1f36e435
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
PODFILE CHECKSUM: 796b297eb3180da7b164556770b43e60cda83073
COCOAPODS: 1.16.2
......@@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
......@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
......@@ -20,10 +22,54 @@
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wx8c32ea248f0c7765</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixin</string>
<string>weixinULAPI</string>
<string>weixinURLParamsAPI</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>dev.banxiaoer.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
......@@ -41,9 +87,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:dev.banxiaoer.net</string>
</array>
</dict>
</plist>
......@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appframe/l10n/gen/app_localizations.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; // 添加这行
import 'config/routes.dart';
......@@ -15,6 +16,16 @@ class App extends StatelessWidget {
routerConfig: router,
title: '班小二',
theme: const CupertinoThemeData(primaryColor: CupertinoColors.systemBlue),
// === 为 iOS 添加本地化配置 ===
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate, // 为Material组件提供本地化
GlobalCupertinoLocalizations.delegate, // 为Cupertino组件提供本地化
GlobalWidgetsLocalizations.delegate, // 定义文本方向等
],
supportedLocales: const [
Locale('zh', 'CN'), // 中文(中国)
Locale('en', 'US'), // 英语(美国)
],
)
: MaterialApp.router(
localizationsDelegates: AppLocalizations.localizationsDelegates,
......@@ -22,5 +33,15 @@ class App extends StatelessWidget {
routerConfig: router,
title: '班小二',
theme: ThemeData(primarySwatch: Colors.blue),
// // === 为 iOS 添加本地化配置 ===
// localizationsDelegates: const [
// GlobalMaterialLocalizations.delegate, // 为Material组件提供本地化
// GlobalCupertinoLocalizations.delegate, // 为Cupertino组件提供本地化
// GlobalWidgetsLocalizations.delegate, // 定义文本方向等
// ],
// supportedLocales: const [
// Locale('zh', 'CN'), // 中文(中国)
// Locale('en', 'US'), // 英语(美国)
// ],
);
}
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();
}
}
......@@ -141,30 +141,30 @@ class WebState extends Equatable {
@override
List<Object?> get props => [
loaded,
title,
beBack,
ip,
sessionCode,
userCode,
classCode,
userType,
stuId,
recorderIsInit,
recordState,
recordPath,
playerIsInit,
playState,
playId,
orientationCmdFlag,
orientationCmdMessage,
windowInfoCmdFlag,
windowInfoCmdMessage,
chooseImageCmdFlag,
chooseImageCmdMessage,
chooseVideoCmdFlag,
chooseVideoCmdMessage,
];
loaded,
title,
beBack,
ip,
sessionCode,
userCode,
classCode,
userType,
stuId,
recorderIsInit,
recordState,
recordPath,
playerIsInit,
playState,
playId,
orientationCmdFlag,
orientationCmdMessage,
windowInfoCmdFlag,
windowInfoCmdMessage,
chooseImageCmdFlag,
chooseImageCmdMessage,
chooseVideoCmdFlag,
chooseVideoCmdMessage,
];
}
class WebCubit extends Cubit<WebState> {
......@@ -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();
},
),
)
......@@ -225,11 +229,14 @@ class WebCubit extends Cubit<WebState> {
final String serverUrl;
if (state.sessionCode == null || state.sessionCode == '') {
serverUrl = '${Constant.localServerTestFileUrl}/login.html';
serverUrl = '${Constant.localServerUrl}/index.html';
// serverUrl = '${Constant.localServerTestFileUrl}/login.html';
// 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}';
}
_controller.loadRequest(Uri.parse(serverUrl));
......@@ -267,6 +274,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';
......@@ -741,7 +756,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: ''));
......@@ -843,7 +857,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: ''));
......@@ -860,14 +873,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';
}
......@@ -33,6 +33,7 @@ import 'package:appframe/services/dispatcher.dart';
import 'package:fluwx/fluwx.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:io' show Platform;
final getIt = GetIt.instance;
......@@ -41,7 +42,9 @@ Future<void> setupLocator() async {
getIt.registerSingleton<Fluwx>(
await (() async {
Fluwx fluwx = Fluwx();
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://univerallink.banxe.cn/link/");
if (Platform.isAndroid || Platform.isIOS) {
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://dev.banxiaoer.net/path/to/wechat/");
}
return fluwx;
})(),
);
......@@ -169,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';
final Fluwx fluwx = Fluwx();
Future<void> registerWechatApi() async {
await fluwx.registerApi(appId: "wx8c32ea248f0c7765", universalLink: "https://dev.banxiaoer.net/path/to/wechat/");
}
......@@ -15,14 +15,18 @@ class AudioPlayHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final url = params['url'] as String;
final url = params['url'] as String;
bool result = await _webCubit!.playAudio(url);
return result;
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 {
bool result = await _webCubit!.startRecording();
return result;
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 {
bool result = await _webCubit!.pauseRecording();
return result;
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 {
bool result = await _webCubit!.resumeRecording();
return result;
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 {
return await _webCubit!.stopRecording();
try {
return await _webCubit!.stopRecording();
} finally {
_unfollowCubit();
}
}
}
......@@ -90,6 +106,10 @@ class AudioRecorderClearHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(dynamic params) async {
return await _webCubit!.clearRecording();
try {
return await _webCubit!.clearRecording();
} finally {
_unfollowCubit();
}
}
}
......@@ -21,30 +21,34 @@ class ChooseImageHandler 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) {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
}
int count = 9;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
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 = 9;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
throw Exception('参数错误');
}
}
_webCubit!.setChooseImageCmdFlag(true, _message!);
_webCubit!.setChooseImageCmdFlag(true, _message!);
} finally {
_unfollowCubit();
}
}
}
......@@ -21,159 +21,34 @@ 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('参数错误');
}
int count = 1;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
}
int maxDuration = 60;
if (params.containsKey('maxDuration')) {
maxDuration = params['maxDuration'] as int;
if (maxDuration < 1 || maxDuration > 600) {
var sourceType = params['sourceType'] as String;
if (sourceType != 'album' && sourceType != 'camera') {
throw Exception('参数错误');
}
}
_webCubit!.setChooseVideoCmdFlag(true, _message!);
int count = 1;
if (params.containsKey('count')) {
count = params['count'] as int;
if (count < 1 || count > 9) {
throw Exception('参数错误');
}
}
int maxDuration = 60;
if (params.containsKey('maxDuration')) {
maxDuration = params['maxDuration'] as int;
if (maxDuration < 1 || maxDuration > 600) {
throw Exception('参数错误');
}
}
_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();
return;
try {
_webCubit!.goLogin();
return;
} finally {
_unfollowCubit();
}
}
}
......@@ -21,6 +21,10 @@ class OrientationHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
_webCubit!.setOrientationCmdFlag(true, _message!);
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;
......@@ -12,7 +10,7 @@ class ScanCodeHandler extends MessageHandler {
Future<dynamic> handleMessage(params) async {
try {
final result = await _webCubit!.goScanCode();
// 返回扫码结果
return result;
} finally {
......@@ -28,4 +26,4 @@ class ScanCodeHandler extends MessageHandler {
void _unfollowCubit() {
this._webCubit = null;
}
}
\ No newline at end of file
}
......@@ -15,13 +15,17 @@ class SetTitleHandler extends MessageHandler {
@override
Future<dynamic> handleMessage(params) async {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
try {
if (params is! Map<String, dynamic>) {
throw Exception('参数错误');
}
final String title = params['title'] as String;
final bool showBack = params['showBack'] as bool;
final String title = params['title'] as String;
final bool showBack = params['showBack'] as bool;
return _webCubit!.setTitle(title, showBack);
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 {
_webCubit!.setWindowInfoCmdFlag(true, _message!);
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 {
var direct = await getExternalStorageDirectory();
_httpDirectory = '${direct?.path}/http_dist_assets';
// var direct = await getApplicationSupportDirectory();
// _httpDirectory = '${direct.path}/http_dist_assets';
if (Platform.isAndroid) {
var direct = await getExternalStorageDirectory();
_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,19 +193,17 @@ 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(
distUrl,
'$httpDirectory/dist.zip',
onReceiveProgress: (received, total) {
if (total != -1) {
print((received / total * 100).toStringAsFixed(0) + "%");
}
},
)
.then((_) {
_extractDist();
});
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 '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) {},
));
}
}
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,
);
}
}
......@@ -6,10 +6,18 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_localization/flutter_localization_plugin.h>
#include <open_file_linux/open_file_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_localization_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin");
flutter_localization_plugin_register_with_registrar(flutter_localization_registrar);
g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
......@@ -3,7 +3,9 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
flutter_localization
open_file_linux
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
......
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
MACOSX_DEPLOYMENT_TARGET = 10.15
\ No newline at end of file
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"
MACOSX_DEPLOYMENT_TARGET = 10.15
\ No newline at end of file
......@@ -7,30 +7,42 @@ import Foundation
import connectivity_plus
import device_info_plus
import ffmpeg_kit_flutter_new_audio
import file_picker
import file_selector_macos
import flutter_image_compress_macos
import flutter_localization
import geolocator_apple
import mobile_scanner
import network_info_plus
import open_file_mac
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
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
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"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
}
platform :osx, '10.14'
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
......
PODS:
- connectivity_plus (0.0.1):
- FlutterMacOS
- device_info_plus (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
- ffmpeg_kit_flutter_new_audio (7.1.1):
- ffmpeg_kit_flutter_new_audio/audio (= 7.1.1)
- FlutterMacOS
- ffmpeg_kit_flutter_new_audio/audio (7.1.1):
- FlutterMacOS
- file_picker (0.0.1):
- FlutterMacOS
- flutter_image_compress_macos (1.0.0):
- FlutterMacOS
- flutter_localization (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- network_info_plus (0.0.1):
- FlutterMacOS
- open_file_mac (0.0.1):
- FlutterMacOS
- package_info_plus (0.0.1):
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- photo_manager (3.7.1):
- Flutter
- FlutterMacOS
- 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):
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- ffmpeg_kit_flutter_new_audio (from `Flutter/ephemeral/.symlinks/plugins/ffmpeg_kit_flutter_new_audio/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- flutter_image_compress_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos`)
- flutter_localization (from `Flutter/ephemeral/.symlinks/plugins/flutter_localization/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
- network_info_plus (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos`)
- open_file_mac (from `Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- 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`)
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)
EXTERNAL SOURCES:
connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
file_selector_macos:
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos
ffmpeg_kit_flutter_new_audio:
:path: Flutter/ephemeral/.symlinks/plugins/ffmpeg_kit_flutter_new_audio/macos
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
flutter_image_compress_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_image_compress_macos/macos
flutter_localization:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_localization/macos
FlutterMacOS:
:path: Flutter/ephemeral
geolocator_apple:
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
mobile_scanner:
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin
network_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos
open_file_mac:
:path: Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
photo_manager:
: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:
:path: Flutter/ephemeral/.symlinks/plugins/video_compress/macos
video_player_avfoundation:
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
webview_flutter_wkwebview:
:path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin
SPEC CHECKSUMS:
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
ffmpeg_kit_flutter_new_audio: d3c98512bcf22c530b56262ac7969a62f3dd60b6
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
flutter_image_compress_macos: e68daf54bb4bf2144c580fd4d151c949cbf492f0
flutter_localization: 3cda759884c7dab4466fe963d97fba723a492666
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
network_info_plus: 21d1cd6a015ccb2fdff06a1fbfa88d54b4e92f61
open_file_mac: 01874b6d6a2c1485ac9b126d7105b99102dea2cf
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
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
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
COCOAPODS: 1.16.2
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/ethanlam/works/gitlab/flutter_pros/appframe.git/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=4d9e48e39f50d70bfc7a740417998f87_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}
\ No newline at end of file
......@@ -19,7 +19,14 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ^3.9.0
sdk: ">=3.5.0 <4.0.0"
fluwx:
app_id: wx8c32ea248f0c7765
debug_logging: true # Logging in debug mode.
ios:
universal_link: https://dev.banxiaoer.net/path/to/wechat/
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
......@@ -30,6 +37,8 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations: # 确保存在这一行
sdk: flutter
archive: ^4.0.7
connectivity_plus: ^7.0.0
device_info_plus: ^11.5.0
......@@ -53,12 +62,15 @@ 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
vibration: ^3.1.3
......@@ -83,7 +95,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
......@@ -103,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
......
cd /Users/ethanlam/works/gitlab/flutter_pros/xehybrid/dist
zip -r dist.zip ./* -x ".*" -x "*/.*"
cp -f /Users/ethanlam/works/gitlab/flutter_pros/xehybrid/dist/dist.zip /Users/ethanlam/works/gitlab/flutter_pros/appframe.git/assets
......@@ -7,17 +7,20 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_localization/flutter_localization_plugin_c_api.h>
#include <geolocator_windows/geolocator_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterLocalizationPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}
......@@ -4,9 +4,10 @@
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
file_selector_windows
flutter_localization
geolocator_windows
permission_handler_windows
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!