Compare commits
2 Commits
af81885441
...
97db3dec46
Author | SHA1 | Date |
---|---|---|
|
97db3dec46 | |
|
94489cf18e |
|
@ -0,0 +1,107 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
class ToastUtil {
|
||||
/// 普通灰色背景提示(仅文字)
|
||||
static void showNormal(BuildContext context, String message, {
|
||||
ToastGravity gravity = ToastGravity.BOTTOM,
|
||||
int duration = 2,
|
||||
}) {
|
||||
_showToast(
|
||||
context: context,
|
||||
message: message,
|
||||
gravity: gravity,
|
||||
duration: duration,
|
||||
);
|
||||
}
|
||||
|
||||
/// 成功提示(带图标)
|
||||
static void showSuccess(BuildContext context, String message, {
|
||||
ToastGravity gravity = ToastGravity.CENTER,
|
||||
int duration = 3,
|
||||
}) {
|
||||
_showToast(
|
||||
context: context,
|
||||
message: message,
|
||||
gravity: gravity,
|
||||
duration: duration,
|
||||
isSuccess: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 失败提示(带图标)
|
||||
static void showError(BuildContext context, String message, {
|
||||
ToastGravity gravity = ToastGravity.CENTER,
|
||||
int duration = 4,
|
||||
}) {
|
||||
_showToast(
|
||||
context: context,
|
||||
message: message,
|
||||
gravity: gravity,
|
||||
duration: duration,
|
||||
isError: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// 内部通用方法
|
||||
static void _showToast({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
required ToastGravity gravity,
|
||||
required int duration,
|
||||
bool isSuccess = false,
|
||||
bool isError = false,
|
||||
}) {
|
||||
// 如果有图标(成功或失败),则使用自定义 Widget
|
||||
if (isSuccess || isError) {
|
||||
final fToast = FToast();
|
||||
fToast.init(context);
|
||||
fToast.showToast(
|
||||
child: _buildIconToast(message, isSuccess: isSuccess),
|
||||
gravity: gravity,
|
||||
toastDuration: Duration(seconds: duration),
|
||||
);
|
||||
}
|
||||
// 普通文字提示
|
||||
else {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: duration > 2 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT,
|
||||
gravity: gravity,
|
||||
backgroundColor: Colors.grey[500],
|
||||
textColor: Colors.white,
|
||||
fontSize: 16.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建带图标的 Toast Widget
|
||||
static Widget _buildIconToast(String message, {bool isSuccess = true}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
color: Colors.grey[850]?.withOpacity(0.9),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isSuccess ? Icons.check : Icons.error_outline,
|
||||
color: isSuccess ? Colors.greenAccent[400] : Colors.redAccent[400],
|
||||
size: 36.0,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,326 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoPlayerWidget extends StatefulWidget {
|
||||
final VideoPlayerController? controller;
|
||||
final String coverUrl;
|
||||
final double aspectRatio;
|
||||
final bool allowSeek;
|
||||
final bool isFullScreen;
|
||||
|
||||
const VideoPlayerWidget({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.coverUrl,
|
||||
required this.aspectRatio,
|
||||
this.allowSeek = true,
|
||||
this.isFullScreen = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState();
|
||||
}
|
||||
|
||||
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
|
||||
bool _visibleControls = true;
|
||||
Timer? _hideTimer;
|
||||
late Timer _positionTimer;
|
||||
Duration _currentPosition = Duration.zero;
|
||||
Duration _totalDuration = Duration.zero;
|
||||
bool _isPlaying = false;
|
||||
final ValueNotifier<double> _sliderValue = ValueNotifier(0.0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startHideTimer();
|
||||
_startPositionTimer();
|
||||
|
||||
if (widget.controller != null) {
|
||||
widget.controller!.addListener(_controllerListener);
|
||||
if (widget.controller!.value.isInitialized) {
|
||||
_updateControllerValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant VideoPlayerWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.controller != oldWidget.controller) {
|
||||
oldWidget.controller?.removeListener(_controllerListener);
|
||||
widget.controller?.addListener(_controllerListener);
|
||||
_updateControllerValues();
|
||||
}
|
||||
}
|
||||
|
||||
void _controllerListener() {
|
||||
if (mounted) setState(() {});
|
||||
|
||||
if (mounted && widget.controller != null) {
|
||||
_updateControllerValues();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateControllerValues() {
|
||||
final controller = widget.controller!;
|
||||
setState(() {
|
||||
_isPlaying = controller.value.isPlaying;
|
||||
_totalDuration = controller.value.duration;
|
||||
_currentPosition = controller.value.position;
|
||||
_sliderValue.value = _currentPosition.inMilliseconds.toDouble();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hideTimer?.cancel();
|
||||
_positionTimer.cancel();
|
||||
_sliderValue.dispose();
|
||||
widget.controller?.removeListener(_controllerListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startHideTimer() {
|
||||
_hideTimer?.cancel();
|
||||
_hideTimer = Timer(const Duration(seconds: 3), () {
|
||||
if (mounted) setState(() => _visibleControls = false);
|
||||
});
|
||||
}
|
||||
|
||||
void _startPositionTimer() {
|
||||
_positionTimer = Timer.periodic(const Duration(milliseconds: 200), (_) {
|
||||
if (mounted && widget.controller != null && widget.controller!.value.isInitialized) {
|
||||
setState(() {
|
||||
_currentPosition = widget.controller!.value.position;
|
||||
_totalDuration = widget.controller!.value.duration;
|
||||
_isPlaying = widget.controller!.value.isPlaying;
|
||||
_sliderValue.value = _currentPosition.inMilliseconds.toDouble();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleControls() {
|
||||
setState(() => _visibleControls = !_visibleControls);
|
||||
if (_visibleControls) _startHideTimer();
|
||||
else _hideTimer?.cancel();
|
||||
}
|
||||
|
||||
void _togglePlayPause() {
|
||||
if (widget.controller == null) return;
|
||||
|
||||
setState(() {
|
||||
_isPlaying = !_isPlaying;
|
||||
});
|
||||
|
||||
if (_isPlaying) {
|
||||
widget.controller!.play();
|
||||
} else {
|
||||
widget.controller!.pause();
|
||||
}
|
||||
_startHideTimer();
|
||||
}
|
||||
|
||||
void _enterFullScreen() {
|
||||
// 锁定横屏
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight,
|
||||
]);
|
||||
// 设置全屏模式
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (ctx) => Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
// 使用SafeArea包裹整个全屏播放器
|
||||
body: SafeArea(
|
||||
top: false, // 顶部不使用安全区域(状态栏区域)
|
||||
bottom: false, // 底部不使用安全区域(导航栏区域)
|
||||
child: Stack(
|
||||
children: [
|
||||
// 全屏视频播放器
|
||||
VideoPlayerWidget(
|
||||
controller: widget.controller,
|
||||
coverUrl: widget.coverUrl,
|
||||
aspectRatio: max(
|
||||
widget.aspectRatio,
|
||||
MediaQuery.of(ctx).size.width / MediaQuery.of(ctx).size.height
|
||||
),
|
||||
allowSeek: widget.allowSeek,
|
||||
isFullScreen: true,
|
||||
),
|
||||
// 添加退出按钮,并包裹在SafeArea中
|
||||
SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black38,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)).then((_) {
|
||||
// 恢复竖屏
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
// 恢复系统UI
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final fullScreenAspectRatio = max(
|
||||
widget.aspectRatio,
|
||||
screenSize.width / screenSize.height
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: _toggleControls,
|
||||
child: Stack(
|
||||
fit: widget.isFullScreen ? StackFit.expand : StackFit.loose,
|
||||
children: [
|
||||
// 视频播放区域
|
||||
if (widget.controller != null && widget.controller!.value.isInitialized)
|
||||
AspectRatio(
|
||||
aspectRatio: widget.isFullScreen ? fullScreenAspectRatio : widget.aspectRatio,
|
||||
child: VideoPlayer(widget.controller!),
|
||||
)
|
||||
else
|
||||
Image.network(
|
||||
widget.coverUrl,
|
||||
fit: BoxFit.cover,
|
||||
width: widget.isFullScreen ? double.infinity : null,
|
||||
height: widget.isFullScreen ? double.infinity : null,
|
||||
),
|
||||
|
||||
// 控制面板
|
||||
if (_visibleControls)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
colors: [Colors.black.withOpacity(0.7), Colors.transparent],
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(
|
||||
_isPlaying ? Icons.pause : Icons.play_arrow,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: _togglePlayPause,
|
||||
),
|
||||
const SizedBox(width: 0),
|
||||
Expanded(
|
||||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: _sliderValue,
|
||||
builder: (context, value, child) {
|
||||
return SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor: Colors.white,
|
||||
inactiveTrackColor: Colors.white54,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.white24,
|
||||
trackHeight: 2,
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||
),
|
||||
child: Slider(
|
||||
value: value,
|
||||
min: 0,
|
||||
max: _totalDuration.inMilliseconds.toDouble(),
|
||||
onChanged: widget.allowSeek && widget.controller != null
|
||||
? (v) {
|
||||
widget.controller!.seekTo(Duration(milliseconds: v.toInt()));
|
||||
setState(() {
|
||||
_currentPosition = Duration(milliseconds: v.toInt());
|
||||
});
|
||||
_sliderValue.value = v;
|
||||
_startHideTimer();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 0),
|
||||
// 使用固定宽度的文本容器,避免进度条跳动
|
||||
SizedBox(
|
||||
width: 110, // 固定宽度,防止进度条长度变化
|
||||
child: Text(
|
||||
'${_formatDuration(_currentPosition)} / ${_formatDuration(_totalDuration)}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontFeatures: [FontFeature.tabularFigures()], // 等宽字体
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 0),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(
|
||||
widget.isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.isFullScreen ? Navigator.of(context).pop() : _enterFullScreen();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDuration(Duration d) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
final hours = d.inHours;
|
||||
final minutes = d.inMinutes.remainder(60);
|
||||
final seconds = d.inSeconds.remainder(60);
|
||||
|
||||
if (hours > 0) {
|
||||
return '${twoDigits(hours)}:${twoDigits(minutes)}:${twoDigits(seconds)}';
|
||||
}
|
||||
return '${twoDigits(minutes)}:${twoDigits(seconds)}';
|
||||
}
|
||||
}
|
|
@ -6,17 +6,6 @@ import 'package:qhd_prevention/tools/tools.dart';
|
|||
import 'HttpManager.dart';
|
||||
|
||||
class ApiService {
|
||||
// static const String basePath = "http://192.168.0.25:28199/";
|
||||
// static const String basePath = "http://192.168.20.240:8500/integrated_whb";
|
||||
// static const String baseFacePath = "http://192.168.0.25:38199/";
|
||||
// 人脸识别服务
|
||||
// static const String baseFacePath = "https://qaaqwh.qhdsafety.com/whb_stu_face/";
|
||||
|
||||
// static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb/";
|
||||
// static const String baseImgPath = "https://file.zcloudchina.com/YTHFile";
|
||||
// static const String adminPath = "https://qaaqwh.qhdsafety.com/integrated_whb/";
|
||||
// static const String projectManagerUrl = 'https://pm.qhdsafety.com/zy-projectManage/';
|
||||
// static const String publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3pM3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OKU6Hzm1ninpWeE+awIDAQAB'
|
||||
// /// 人脸识别服务
|
||||
// static const String baseFacePath =
|
||||
// "https://qaaqwh.qhdsafety.com/whb_stu_face/";
|
||||
|
@ -35,7 +24,6 @@ class ApiService {
|
|||
// static const String projectManagerUrl =
|
||||
// 'https://pm.qhdsafety.com/zy-projectManage';
|
||||
|
||||
|
||||
/// 人脸识别服务
|
||||
static const String baseFacePath =
|
||||
"https://qaaqwh.qhdsafety.com/whb_stu_face/";
|
||||
|
@ -54,7 +42,6 @@ class ApiService {
|
|||
static const String projectManagerUrl =
|
||||
'https://pm.qhdsafety.com/zy-projectManage';
|
||||
|
||||
|
||||
/// RSA 公钥
|
||||
static const publicKey = '''
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
|
@ -65,9 +52,6 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
-----END PUBLIC KEY-----
|
||||
''';
|
||||
|
||||
|
||||
|
||||
|
||||
/// 登录验证接口
|
||||
static Future<Map<String, dynamic>> loginCheck(String keydataVal) {
|
||||
return HttpManager().request(
|
||||
|
@ -320,6 +304,35 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
},
|
||||
);
|
||||
}
|
||||
static Future<Map<String, dynamic>> fnGetUserFaceTime(int FACE_TIME) {
|
||||
return HttpManager().request(
|
||||
baseFacePath,
|
||||
'/app/user/getUserFaceTime',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'loading':false,
|
||||
'FACE_TIME': FACE_TIME,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
|
||||
},
|
||||
);
|
||||
}
|
||||
/// 清除人脸时间
|
||||
static Future<Map<String, dynamic>> fnClearUserFaceTime() {
|
||||
return HttpManager().request(
|
||||
baseFacePath,
|
||||
'/app/user/clearUserFaceTime',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'loading':false,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static Future<Map<String, dynamic>> fnGetVideoPlayProgress(String VIDEOCOURSEWARE_ID, String CURRICULUM_ID, String CLASS_ID, String STUDENT_ID) {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
|
@ -343,6 +356,7 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
'/app/edu/coursestudyvideorecord/save',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'USERNAME': SessionService.instance.username,
|
||||
'VIDEOCOURSEWARE_ID': VIDEOCOURSEWARE_ID,
|
||||
'CURRICULUM_ID': CURRICULUM_ID,
|
||||
'CHAPTER_ID': CHAPTER_ID,
|
||||
|
@ -352,7 +366,6 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
'CLASSCURRICULUM_ID': CLASSCURRICULUM_ID,
|
||||
'STUDENT_ID': STUDENT_ID,
|
||||
'loading': false,
|
||||
'USER_NAME': SessionService.instance.username,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
|
||||
|
@ -551,7 +564,7 @@ U6Hzm1ninpWeE+awIDAQAB
|
|||
);
|
||||
}
|
||||
/// 更新人脸信息
|
||||
static Future<Map<String, dynamic>> reloadMyFace(String imagePath) async {
|
||||
static Future<Map<String, dynamic>> reloadMyFace(String imagePath, String studentId) async {
|
||||
final file = File(imagePath);
|
||||
if (!await file.exists()) {
|
||||
throw ApiException('file_not_found', '图片不存在:$imagePath');
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/// 全局接口异常
|
||||
|
@ -6,6 +7,7 @@ class ApiException implements Exception {
|
|||
final String result;
|
||||
final String message;
|
||||
ApiException(this.result, this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'ApiException($result): $message';
|
||||
}
|
||||
|
@ -30,11 +32,32 @@ class HttpManager {
|
|||
factory HttpManager() => _instance;
|
||||
late final Dio _dio;
|
||||
|
||||
// 添加401处理回调
|
||||
static VoidCallback? onUnauthorized;
|
||||
|
||||
void _initInterceptors() {
|
||||
_dio.interceptors
|
||||
..add(LogInterceptor(request: true, responseBody: true, error: true))
|
||||
..add(InterceptorsWrapper(onError: (err, handler) {
|
||||
// 全局错误处理,可根据 err.response?.statusCode 或 err.type 做不同处理
|
||||
// 捕获401错误
|
||||
if (err.response?.statusCode == 401) {
|
||||
// 触发全局登出回调
|
||||
onUnauthorized?.call();
|
||||
// 创建自定义异常
|
||||
final apiException = ApiException(
|
||||
'提示',
|
||||
'您的账号已在其他设备登录,已自动下线'
|
||||
);
|
||||
// 直接抛出业务异常,跳过后续错误处理
|
||||
return handler.reject(
|
||||
DioException(
|
||||
requestOptions: err.requestOptions,
|
||||
error: apiException,
|
||||
response: err.response,
|
||||
type: DioExceptionType.badResponse,
|
||||
),
|
||||
);
|
||||
}
|
||||
handler.next(err);
|
||||
}));
|
||||
}
|
||||
|
@ -83,7 +106,7 @@ class HttpManager {
|
|||
);
|
||||
break;
|
||||
case Method.post:
|
||||
resp = await _dio.post(
|
||||
resp = await _dio.post(
|
||||
url,
|
||||
data: data,
|
||||
queryParameters: params,
|
||||
|
@ -92,7 +115,11 @@ class HttpManager {
|
|||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
// 网络或服务器错误
|
||||
// 如果已经是ApiException类型(401转换的)
|
||||
if (e.error is ApiException) {
|
||||
throw e.error as ApiException;
|
||||
}
|
||||
// 其他网络错误
|
||||
throw ApiException('network_error', e.message ?? e.toString());
|
||||
}
|
||||
|
||||
|
@ -136,7 +163,11 @@ extension HttpManagerUpload on HttpManager {
|
|||
|
||||
return json;
|
||||
} on DioException catch (e) {
|
||||
// 如果已经是ApiException类型(401转换的)
|
||||
if (e.error is ApiException) {
|
||||
throw e.error as ApiException;
|
||||
}
|
||||
throw ApiException('network_error', e.message ?? e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/pages/home/study/study_detail_page.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import './pages/login_page.dart'; // 导入登录页面
|
||||
import './pages/main_tab.dart'; // 导入主页(带TabBar的页面)
|
||||
import './pages/login_page.dart';
|
||||
import './pages/main_tab.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'http/HttpManager.dart';
|
||||
|
||||
// 全局导航键
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
// 全局消息控制器
|
||||
class GlobalMessage {
|
||||
static void showError(String message) {
|
||||
final context = navigatorKey.currentContext;
|
||||
if (context != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
// 确保Flutter引擎初始化完成
|
||||
|
@ -15,6 +36,25 @@ void main() async {
|
|||
// 获取登录状态
|
||||
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
|
||||
|
||||
// 初始化HTTP管理器
|
||||
HttpManager.onUnauthorized = () async {
|
||||
// 清除登录状态
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('isLoggedIn', false);
|
||||
await prefs.remove('token'); // 如果有token的话
|
||||
|
||||
// 导航到登录页
|
||||
navigatorKey.currentState?.pushNamedAndRemoveUntil(
|
||||
'/login',
|
||||
(route) => false,
|
||||
);
|
||||
|
||||
// 等一帧让页面构建完成
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
GlobalMessage.showError('会话已过期,请重新登录');
|
||||
});
|
||||
};
|
||||
|
||||
runApp(MyApp(isLoggedIn: isLoggedIn));
|
||||
}
|
||||
|
||||
|
@ -27,6 +67,7 @@ class MyApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '',
|
||||
navigatorKey: navigatorKey, // 设置全局导航键
|
||||
builder: (context, child) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent, // 让空白区域也能点击
|
||||
|
@ -50,10 +91,20 @@ class MyApp extends StatelessWidget {
|
|||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 根据登录状态决定初始页面
|
||||
home: isLoggedIn ? const MainPage() : const LoginPage(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
routes: {
|
||||
'/login': (_) => const LoginPage(),
|
||||
// ... 其他路由
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -494,9 +494,9 @@ class _HomePageState extends State<HomePage> {
|
|||
final checkJson =
|
||||
await ApiService.getSafetyEnvironmentalInspectionCount();
|
||||
setState(() {
|
||||
int confirmCount = int.parse(checkJson['confirmCount']['confirmCount']);
|
||||
int repulseCount = int.parse(checkJson['repulseCount']['repulseCount']);
|
||||
int repulseAndCheckCount = int.parse(checkJson['repulseAndCheckCount']['repulseAndCheckCount']);
|
||||
int confirmCount = checkJson['confirmCount']['confirmCount'];
|
||||
int repulseCount = checkJson['repulseCount']['repulseCount'];
|
||||
int repulseAndCheckCount = checkJson['repulseAndCheckCount']['repulseAndCheckCount'];
|
||||
|
||||
_safetyEnvironmentalInspection = confirmCount + repulseCount + repulseAndCheckCount;
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:async';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||
import 'package:qhd_prevention/customWidget/toast_util.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
import '../../../http/ApiService.dart';
|
||||
|
@ -15,8 +17,8 @@ class FaceRecognitionPage extends StatefulWidget {
|
|||
|
||||
const FaceRecognitionPage({
|
||||
Key? key,
|
||||
this.studentId = '',
|
||||
required this.mode,
|
||||
required this.studentId,
|
||||
this.mode = FaceMode.auto,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
@ -27,8 +29,6 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage> {
|
|||
CameraController? _cameraController;
|
||||
Timer? _timer;
|
||||
int _attempts = 0;
|
||||
String _message = '';
|
||||
String _tip = '请将人脸置于圆圈内';
|
||||
|
||||
static const int _maxAttempts = 8;
|
||||
static const Duration _interval = Duration(seconds: 2);
|
||||
|
@ -61,54 +61,76 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _captureAndUpload() async {
|
||||
if (_isManualMode) {
|
||||
setState(() => _message = '请将人脸置于圆圈内');
|
||||
} else {
|
||||
if (_attempts >= _maxAttempts) return _onTimeout();
|
||||
_attempts++;
|
||||
}
|
||||
|
||||
try {
|
||||
final pic = await _cameraController!.takePicture();
|
||||
final res = await ApiService.getUserFace(pic.path, widget.studentId);
|
||||
if (res['result'] == 'success') {
|
||||
_onSuccess();
|
||||
} else {
|
||||
setState(() => _message = '识别失败,请重试');
|
||||
}
|
||||
} catch (_) {
|
||||
setState(() => _message = '发生错误,请重试');
|
||||
}
|
||||
/// 显示加载弹窗
|
||||
void _showLoading() {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
/// 隐藏弹窗
|
||||
void _hideLoading() {
|
||||
if (Navigator.canPop(context)) Navigator.pop(context);
|
||||
}
|
||||
/// 定时上传
|
||||
Future<void> _captureAndUpload() async {
|
||||
if (!_isManualMode) {
|
||||
if (_attempts >= _maxAttempts) return _onTimeout();
|
||||
_attempts++;
|
||||
try {
|
||||
final pic = await _cameraController!.takePicture();
|
||||
final res = await ApiService.getUserFace(pic.path, widget.studentId);
|
||||
if (res['result'] == 'success') {
|
||||
_onSuccess();
|
||||
}
|
||||
} catch (_) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 手动上传验证
|
||||
Future<void> _captureAndReload() async {
|
||||
setState(() => _message = '上传中...');
|
||||
_showLoading();
|
||||
try {
|
||||
final pic = await _cameraController!.takePicture();
|
||||
final res = await ApiService.reloadMyFace(pic.path);
|
||||
final res = await ApiService.reloadMyFace(pic.path, widget.studentId);
|
||||
_hideLoading();
|
||||
if (res['result'] == 'success') {
|
||||
_onSuccess();
|
||||
} else {
|
||||
setState(() => _message = '验证失败,请重试');
|
||||
ToastUtil.showError(context, '验证失败,请重试');
|
||||
|
||||
}
|
||||
} catch (_) {
|
||||
setState(() => _message = '发生错误,请重试');
|
||||
_hideLoading();
|
||||
_showToast('发生错误,请重试');
|
||||
ToastUtil.showError(context, '发生错误,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
void _onSuccess() {
|
||||
_timer?.cancel();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('验证成功')));
|
||||
if (widget.mode == FaceMode.manual) {
|
||||
ToastUtil.showSuccess(context, '已更新人脸信息');
|
||||
Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true));
|
||||
return;
|
||||
}
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 800), () => Navigator.of(context).pop(true));
|
||||
}
|
||||
|
||||
void _onTimeout() {
|
||||
_timer?.cancel();
|
||||
setState(() => _message = '人脸超时,请重新识别!');
|
||||
ToastUtil.showError(context, '人脸超时,请重新识别!');
|
||||
Future.delayed(const Duration(seconds: 3), () => Navigator.of(context).pop(false));
|
||||
}
|
||||
|
||||
void _showToast(String msg) {
|
||||
ToastUtil.showNormal(context, msg);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_cameraController == null || !_cameraController!.value.isInitialized) {
|
||||
|
@ -157,7 +179,7 @@ class _FaceRecognitionPageState extends State<FaceRecognitionPage> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_tip,
|
||||
'请将人脸置于圆圈内',
|
||||
style: const TextStyle(fontSize: 18, color: Colors.black87),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:qhd_prevention/pages/home/study/take_exam_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/study/study_practise_page.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
|
||||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||||
import 'package:qhd_prevention/http/ApiService.dart';
|
||||
import 'package:qhd_prevention/pages/home/study/study_practise_page.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../../../customWidget/video_player_widget.dart';
|
||||
import '../../../http/HttpManager.dart';
|
||||
import 'face_ecognition_page.dart';
|
||||
|
||||
class StudyDetailPage extends StatefulWidget {
|
||||
final Map studyDetailDetail;
|
||||
final String studentId;
|
||||
|
||||
const StudyDetailPage(this.studyDetailDetail, this.studentId, {super.key});
|
||||
|
||||
@override
|
||||
|
@ -23,12 +25,14 @@ class StudyDetailPage extends StatefulWidget {
|
|||
class _StudyDetailPageState extends State<StudyDetailPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
VideoPlayerController? _videoController;
|
||||
|
||||
Map<String, dynamic>? _info;
|
||||
List<dynamic> _videoList = [];
|
||||
bool _loading = true;
|
||||
|
||||
// player controller extracted
|
||||
VideoPlayerController? _videoController;
|
||||
String _videoCoverUrl = '';
|
||||
|
||||
Timer? _faceTimer;
|
||||
bool _throttleFlag = false;
|
||||
late int _faceTime;
|
||||
|
@ -45,8 +49,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
void initState() {
|
||||
super.initState();
|
||||
_classId = widget.studyDetailDetail['CLASS_ID'] ?? '';
|
||||
_classCurriculumId =
|
||||
widget.studyDetailDetail['CLASSCURRICULUM_ID'] ?? '';
|
||||
_classCurriculumId = widget.studyDetailDetail['CLASSCURRICULUM_ID'] ?? '';
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showFaceIntro();
|
||||
|
@ -88,7 +91,11 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
final pd = res['pd'] ?? {};
|
||||
_faceTime = int.tryParse(pd['FACE_TIME']?.toString() ?? '10') ?? 10;
|
||||
_videoList = List.from(pd['VIDEOLIST'] ?? []);
|
||||
// 计算 percent
|
||||
// set initial face time
|
||||
if (pd['ISFACE'] == '1') {
|
||||
await ApiService.fnSetUserFaceTime(_faceTime);
|
||||
}
|
||||
// compute percent
|
||||
for (var item in _videoList) {
|
||||
if (item['nodes'] is List && (item['nodes'] as List).isNotEmpty) {
|
||||
for (var node in item['nodes']) {
|
||||
|
@ -130,11 +137,14 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
}
|
||||
|
||||
Future<void> _onVideoTap(
|
||||
Map<String, dynamic> data, bool hasNodes, int fi, int ni) async {
|
||||
// 停掉上一轮人脸定时
|
||||
Map<String, dynamic> data,
|
||||
bool hasNodes,
|
||||
int fi,
|
||||
int ni,
|
||||
) async {
|
||||
// clear face timer on backend
|
||||
await ApiService.fnClearUserFaceTime();
|
||||
_faceTimer?.cancel();
|
||||
|
||||
// 上报上一个视频进度
|
||||
if (_currentVideoData != null && _lastReported > Duration.zero) {
|
||||
await _submitPlayTime(end: false, seconds: _lastReported.inSeconds);
|
||||
}
|
||||
|
@ -145,31 +155,29 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
_currentFirstIndex = fi;
|
||||
_currentNodeIndex = ni;
|
||||
|
||||
// 先暂停任何正在播放的视频
|
||||
// pause existing
|
||||
_videoController?.pause();
|
||||
|
||||
// 统一认证逻辑
|
||||
await _navigateFaceIfNeeded(() async {
|
||||
if ((data['IS_VIDEO'] ?? 0) == 1) {
|
||||
// 资料
|
||||
// document
|
||||
if (data['VIDEOFILES'] != null) {
|
||||
await pushPage(
|
||||
StudyPractisePage(data['VIDEOCOURSEWARE_ID']),
|
||||
context,
|
||||
);
|
||||
await _submitPlayTime(end: true, seconds: int.parse(data['VIDEOTIME'] ?? '0'));
|
||||
await _submitPlayTime(
|
||||
end: true,
|
||||
seconds: int.parse(data['VIDEOTIME'] ?? '0'),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('课件文件资源已失效,请联系管理员')),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 视频
|
||||
// video
|
||||
await _getVideoPlayInfo(data['VIDEOCOURSEWARE_ID']);
|
||||
// 播放并启动定时
|
||||
_videoController!
|
||||
..play()
|
||||
..addListener(_onTimeUpdate);
|
||||
_startFaceTimer();
|
||||
}
|
||||
});
|
||||
|
@ -178,10 +186,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
Future<void> _navigateFaceIfNeeded(FutureOr<void> Function() onPass) async {
|
||||
if (_info?['ISFACE'] == '1') {
|
||||
final passed = await pushPage<bool>(
|
||||
FaceRecognitionPage(
|
||||
studentId: widget.studentId,
|
||||
mode: FaceMode.auto,
|
||||
),
|
||||
FaceRecognitionPage(studentId: widget.studentId, mode: FaceMode.auto),
|
||||
context,
|
||||
);
|
||||
if (passed == true) {
|
||||
|
@ -199,24 +204,26 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
Future<void> _getVideoPlayInfo(String vidId) async {
|
||||
final res = await ApiService.fnGetVideoPlayInfo(vidId);
|
||||
final url = res['videoList']?[0]?['playURL'] ?? '';
|
||||
_videoCoverUrl = res['videoBase']?['coverURL'] ?? '';
|
||||
|
||||
final prog = await ApiService.fnGetVideoPlayProgress(
|
||||
vidId,
|
||||
_currentVideoData!['CURRICULUM_ID'],
|
||||
_classId,
|
||||
widget.studentId,
|
||||
);
|
||||
final seen = prog['pd']?['RESOURCETIME'] ?? 0;
|
||||
final seen = (double.tryParse(prog['pd']?['RESOURCETIME']) ?? 0.0).toInt();
|
||||
|
||||
_videoController?.removeListener(_onTimeUpdate);
|
||||
_videoController?.dispose();
|
||||
_videoController = VideoPlayerController.networkUrl(Uri.parse(url))
|
||||
..initialize().then((_) {
|
||||
setState(() {
|
||||
_videoController!
|
||||
..seekTo(Duration(seconds: seen))
|
||||
..play()
|
||||
..addListener(_onTimeUpdate);
|
||||
});
|
||||
});
|
||||
_videoController = VideoPlayerController.networkUrl(Uri.parse(url));
|
||||
await _videoController!.initialize();
|
||||
setState(() {});
|
||||
|
||||
_videoController!
|
||||
..seekTo(Duration(seconds: seen))
|
||||
..play()
|
||||
..addListener(_onTimeUpdate);
|
||||
}
|
||||
|
||||
void _onTimeUpdate() {
|
||||
|
@ -226,9 +233,14 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
_throttleFlag = true;
|
||||
_lastReported = curr;
|
||||
_submitPlayTime(end: false, seconds: curr.inSeconds)
|
||||
.whenComplete(() => Future.delayed(const Duration(seconds: 1), () {
|
||||
_throttleFlag = false;
|
||||
}));
|
||||
.whenComplete(() => _throttleFlag = false);
|
||||
}
|
||||
final pos = _videoController!.value.position;
|
||||
final dur = _videoController!.value.duration;
|
||||
if (pos >= dur) {
|
||||
_submitPlayTime(end: true, seconds: dur.inSeconds);
|
||||
ApiService.fnClearUserFaceTime();
|
||||
_faceTimer?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,95 +249,101 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
required int seconds,
|
||||
}) async {
|
||||
if (_currentVideoData == null) return;
|
||||
final pd = (await ApiService.fnSubmitPlayTime(
|
||||
_currentVideoData!['VIDEOCOURSEWARE_ID'],
|
||||
_currentVideoData!['CURRICULUM_ID'],
|
||||
end ? '1' : '0',
|
||||
seconds,
|
||||
_currentVideoData!['CHAPTER_ID'],
|
||||
widget.studentId,
|
||||
_classCurriculumId,
|
||||
_classId,
|
||||
))['pd']!;
|
||||
// 更新列表 percent
|
||||
final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 0;
|
||||
final resT = pd['RESOURCETIME'] ?? seconds;
|
||||
final pct = comp
|
||||
? 100
|
||||
: (resT / (_currentVideoData!['VIDEOTIME'] ?? 1) * 100).clamp(0, 100);
|
||||
final str = '${pct.floor()}%';
|
||||
setState(() {
|
||||
if (_hasNodes) {
|
||||
_videoList[_currentFirstIndex]['nodes'][_currentNodeIndex]
|
||||
['percent'] = str;
|
||||
} else {
|
||||
_videoList[_currentFirstIndex]['percent'] = str;
|
||||
}
|
||||
});
|
||||
|
||||
// 结束且可考试
|
||||
if (end && pd['CANEXAM'] == '1') {
|
||||
_videoController?.pause();
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => CustomAlertDialog(
|
||||
title: '提示',
|
||||
content: '当前任务内所有课程均已学完,是否直接参加考试?',
|
||||
confirmText: '是',
|
||||
cancelText: '否',
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
if (ok) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
'/course_exam',
|
||||
arguments: {
|
||||
'STAGEEXAMPAPERINPUT_ID': pd['paper']['STAGEEXAMPAPERINPUT_ID'],
|
||||
try {
|
||||
final pd = (await ApiService.fnSubmitPlayTime(
|
||||
_currentVideoData!['VIDEOCOURSEWARE_ID'],
|
||||
_currentVideoData!['CURRICULUM_ID'],
|
||||
end ? '1' : '0',
|
||||
seconds,
|
||||
_currentVideoData!['CHAPTER_ID'],
|
||||
widget.studentId,
|
||||
_classCurriculumId,
|
||||
_classId,
|
||||
))['pd']!;
|
||||
|
||||
// 更新进度显示
|
||||
final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 0;
|
||||
final resT = pd['RESOURCETIME'] ?? seconds;
|
||||
final pct = comp
|
||||
? 100
|
||||
: (resT / (_currentVideoData!['VIDEOTIME'] ?? 1) * 100)
|
||||
.clamp(0, 100);
|
||||
final str = '${pct.floor()}%';
|
||||
setState(() {
|
||||
if (_hasNodes) {
|
||||
_videoList[_currentFirstIndex]['nodes'][_currentNodeIndex]
|
||||
['percent'] = str;
|
||||
} else {
|
||||
_videoList[_currentFirstIndex]['percent'] = str;
|
||||
}
|
||||
});
|
||||
|
||||
// 如果结束且可考试,弹框
|
||||
if (end && pd['CANEXAM'] == '1') {
|
||||
_videoController?.pause();
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => CustomAlertDialog(
|
||||
title: '提示',
|
||||
content: '当前任务内所有课程均已学完,是否直接参加考试?',
|
||||
confirmText: '是',
|
||||
cancelText: '否',
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
if (ok) {
|
||||
final arguments = {
|
||||
'STAGEEXAMPAPERINPUT_ID':
|
||||
pd['paper']['STAGEEXAMPAPERINPUT_ID'],
|
||||
'CLASS_ID': _classId,
|
||||
'STUDENT_ID': widget.studentId,
|
||||
'NUMBEROFEXAMS': pd['NUMBEROFEXAMS'],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
_videoController?.play();
|
||||
};
|
||||
pushPage(TakeExamPage(arguments), context);
|
||||
} else {
|
||||
_videoController?.play();
|
||||
}
|
||||
}
|
||||
} on ApiException catch (e) {
|
||||
// 如果是 401 ,就登出并跳转登录
|
||||
|
||||
// 其他错误继续抛出
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void _startFaceTimer() {
|
||||
_faceTimer = Timer.periodic(Duration(seconds: _faceTime), (_) {
|
||||
_videoController?.pause();
|
||||
_showFaceAuthOnce();
|
||||
_faceTimer = Timer.periodic(Duration(seconds: _faceTime), (_) async {
|
||||
final res = await ApiService.fnGetUserFaceTime(_faceTime);
|
||||
final isPlaying = _videoController?.value.isPlaying == true;
|
||||
final isVideo = _currentVideoData?['IS_VIDEO'] == 0;
|
||||
final needAuth = res['data'] == false;
|
||||
|
||||
if (isPlaying && isVideo && needAuth) {
|
||||
_videoController!.pause();
|
||||
_videoController!.removeListener(_onTimeUpdate);
|
||||
await _showFaceAuthOnce();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showFaceAuthOnce() async {
|
||||
final passed = await pushPage<bool>(
|
||||
FaceRecognitionPage(
|
||||
studentId: widget.studentId,
|
||||
mode: FaceMode.auto,
|
||||
),
|
||||
FaceRecognitionPage(studentId: widget.studentId, mode: FaceMode.auto),
|
||||
context,
|
||||
);
|
||||
if (passed == true) {
|
||||
_videoController?.play();
|
||||
await _videoController?.play();
|
||||
setState(() {});
|
||||
_faceTimer?.cancel();
|
||||
_startFaceTimer();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('人脸验证未通过,视频已暂停')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String secondsCount(dynamic sec) {
|
||||
final s = int.tryParse('$sec') ?? 0;
|
||||
final h = s ~/ 3600;
|
||||
final m = (s % 3600) ~/ 60;
|
||||
final ss = s % 60;
|
||||
return '${h.toString().padLeft(2, '0')}:'
|
||||
'${m.toString().padLeft(2, '0')}:'
|
||||
'${ss.toString().padLeft(2, '0')}';
|
||||
|
||||
void _controllerListener() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -344,16 +362,12 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
children: [
|
||||
SizedBox(
|
||||
height: 250,
|
||||
child: _videoController != null &&
|
||||
_videoController!.value.isInitialized
|
||||
? AspectRatio(
|
||||
aspectRatio: _videoController!.value.aspectRatio,
|
||||
child: VideoPlayer(_videoController!),
|
||||
)
|
||||
: Image.network(
|
||||
ApiService.baseImgPath + (info['COVERPATH'] ?? ''),
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
child: VideoPlayerWidget(
|
||||
controller: _videoController,
|
||||
coverUrl: _videoCoverUrl.isNotEmpty
|
||||
? ApiService.baseImgPath + _videoCoverUrl
|
||||
: ApiService.baseImgPath + (info['COVERPATH'] ?? ''),
|
||||
aspectRatio: _videoController?.value.aspectRatio ?? 16/9,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
|
@ -363,7 +377,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
child: Text(
|
||||
info['CURRICULUMNAME'] ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 20, fontWeight: FontWeight.bold),
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
@ -371,8 +387,10 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
color: Colors.white,
|
||||
child: TabBar(
|
||||
indicatorColor: Colors.blue,
|
||||
labelStyle:
|
||||
const TextStyle(fontSize: 16, color: Colors.black87),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black87,
|
||||
),
|
||||
controller: _tabController,
|
||||
tabs: const [Tab(text: '课件目录'), Tab(text: '详情')],
|
||||
),
|
||||
|
@ -381,10 +399,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildVideoList(),
|
||||
_buildDetailView(info),
|
||||
],
|
||||
children: [_buildVideoList(), _buildDetailView(info)],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -402,16 +417,19 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
if (nodes != null && nodes.isNotEmpty) {
|
||||
return ExpansionTile(
|
||||
title: Text(item['NAME'] ?? ''),
|
||||
children: nodes
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => _buildVideoItem(
|
||||
e.value as Map<String, dynamic>,
|
||||
true,
|
||||
idx,
|
||||
e.key,
|
||||
))
|
||||
.toList(),
|
||||
children:
|
||||
nodes
|
||||
.asMap()
|
||||
.entries
|
||||
.map(
|
||||
(e) => _buildVideoItem(
|
||||
e.value as Map<String, dynamic>,
|
||||
true,
|
||||
idx,
|
||||
e.key,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
return _buildVideoItem(item, false, idx, 0);
|
||||
|
@ -420,7 +438,11 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
}
|
||||
|
||||
Widget _buildVideoItem(
|
||||
Map<String, dynamic> m, bool hasNodes, int fi, int ni) {
|
||||
Map<String, dynamic> m,
|
||||
bool hasNodes,
|
||||
int fi,
|
||||
int ni,
|
||||
) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.all(20),
|
||||
|
@ -429,11 +451,13 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.file_copy_rounded,
|
||||
color: Colors.grey, size: 20),
|
||||
const Icon(Icons.file_copy_rounded, color: Colors.grey, size: 20),
|
||||
Expanded(
|
||||
child: Text(m['NAME'] ?? '',
|
||||
style: const TextStyle(fontSize: 14))),
|
||||
child: Text(
|
||||
m['NAME'] ?? '',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
@ -441,8 +465,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
onTap: () => _onVideoTap(m, hasNodes, fi, ni),
|
||||
child: Container(
|
||||
height: 80,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFAFAFA),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
|
@ -451,28 +474,39 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(m['COURSEWARENAME'] ?? '',
|
||||
style: const TextStyle(fontSize: 14)),
|
||||
child: Text(
|
||||
m['COURSEWARENAME'] ?? '',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("进度:${m['percent']}",
|
||||
style: const TextStyle(color: Colors.blue)),
|
||||
Text(
|
||||
"进度:${m['percent']}",
|
||||
style: const TextStyle(color: Colors.blue),
|
||||
),
|
||||
if (m['IS_VIDEO'] == 0) ...[
|
||||
Text(secondsCount(m['VIDEOTIME'])),
|
||||
const Icon(Icons.play_circle, color: Colors.blue),
|
||||
],
|
||||
CustomButton(
|
||||
onPressed: () =>
|
||||
pushPage(StudyPractisePage(m['VIDEOCOURSEWARE_ID']), context),
|
||||
onPressed:
|
||||
() => pushPage(
|
||||
StudyPractisePage(m['VIDEOCOURSEWARE_ID']),
|
||||
context,
|
||||
),
|
||||
text: "课后练习",
|
||||
backgroundColor: Colors.blue,
|
||||
height: 30,
|
||||
textStyle:
|
||||
const TextStyle(fontSize: 12, color: Colors.white),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 2, horizontal: 12),
|
||||
vertical: 2,
|
||||
horizontal: 12,
|
||||
),
|
||||
borderRadius: 15,
|
||||
),
|
||||
],
|
||||
|
@ -480,7 +514,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -492,8 +526,10 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(info['CURRICULUMINTRODUCE'] ?? '',
|
||||
style: const TextStyle(fontSize: 16)),
|
||||
Text(
|
||||
info['CURRICULUMINTRODUCE'] ?? '',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (info['COVERPATH'] != null)
|
||||
Image.network(ApiService.baseImgPath + info['COVERPATH']),
|
||||
|
@ -502,3 +538,4 @@ class _StudyDetailPageState extends State<StudyDetailPage>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/pages/my_appbar.dart';
|
||||
|
||||
class TakeExamPage extends StatefulWidget {
|
||||
const TakeExamPage(this.arguments,{super.key});
|
||||
final Map<String, dynamic> arguments;
|
||||
@override
|
||||
State<TakeExamPage> createState() => _TakeExamPageState();
|
||||
}
|
||||
|
||||
class _TakeExamPageState extends State<TakeExamPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: MyAppbar(title: '开始考试'),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -238,7 +238,6 @@ class ClickUtil {
|
|||
_canClick = true;
|
||||
});
|
||||
} else {
|
||||
// 可替换成 Toast
|
||||
debugPrint('请稍后点击');
|
||||
}
|
||||
}
|
||||
|
|
224
pubspec.lock
224
pubspec.lock
|
@ -6,7 +6,7 @@ packages:
|
|||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
asn1lib:
|
||||
|
@ -21,16 +21,16 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
version: "2.12.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
camera:
|
||||
|
@ -78,7 +78,7 @@ packages:
|
|||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
|
@ -86,7 +86,7 @@ packages:
|
|||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
|
@ -94,7 +94,7 @@ packages:
|
|||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
connectivity_plus:
|
||||
|
@ -102,7 +102,7 @@ packages:
|
|||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
connectivity_plus_platform_interface:
|
||||
|
@ -110,7 +110,7 @@ packages:
|
|||
description:
|
||||
name: connectivity_plus_platform_interface
|
||||
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
convert:
|
||||
|
@ -126,7 +126,7 @@ packages:
|
|||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
|
@ -134,7 +134,7 @@ packages:
|
|||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
csslib:
|
||||
|
@ -142,7 +142,7 @@ packages:
|
|||
description:
|
||||
name: csslib
|
||||
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
cupertino_icons:
|
||||
|
@ -150,7 +150,7 @@ packages:
|
|||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dbus:
|
||||
|
@ -158,7 +158,7 @@ packages:
|
|||
description:
|
||||
name: dbus
|
||||
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
dio:
|
||||
|
@ -190,7 +190,7 @@ packages:
|
|||
description:
|
||||
name: extended_image
|
||||
sha256: f6cbb1d798f51262ed1a3d93b4f1f2aa0d76128df39af18ecb77fa740f88b2e0
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "10.0.1"
|
||||
extended_image_library:
|
||||
|
@ -198,23 +198,23 @@ packages:
|
|||
description:
|
||||
name: extended_image_library
|
||||
sha256: "1f9a24d3a00c2633891c6a7b5cab2807999eb2d5b597e5133b63f49d113811fe"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
version: "1.3.2"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
file:
|
||||
|
@ -222,7 +222,7 @@ packages:
|
|||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector_linux:
|
||||
|
@ -230,7 +230,7 @@ packages:
|
|||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.9.3+2"
|
||||
file_selector_macos:
|
||||
|
@ -238,7 +238,7 @@ packages:
|
|||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.9.4+3"
|
||||
file_selector_platform_interface:
|
||||
|
@ -246,7 +246,7 @@ packages:
|
|||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
file_selector_windows:
|
||||
|
@ -254,7 +254,7 @@ packages:
|
|||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.9.3+4"
|
||||
flutter:
|
||||
|
@ -267,7 +267,7 @@ packages:
|
|||
description:
|
||||
name: flutter_html
|
||||
sha256: "38a2fd702ffdf3243fb7441ab58aa1bc7e6922d95a50db76534de8260638558d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
flutter_lints:
|
||||
|
@ -275,7 +275,7 @@ packages:
|
|||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
|
@ -283,7 +283,7 @@ packages:
|
|||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.28"
|
||||
flutter_test:
|
||||
|
@ -309,7 +309,7 @@ packages:
|
|||
description:
|
||||
name: html
|
||||
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.15.6"
|
||||
http:
|
||||
|
@ -317,7 +317,7 @@ packages:
|
|||
description:
|
||||
name: http
|
||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
http_client_helper:
|
||||
|
@ -325,7 +325,7 @@ packages:
|
|||
description:
|
||||
name: http_client_helper
|
||||
sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
http_parser:
|
||||
|
@ -333,7 +333,7 @@ packages:
|
|||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image_picker:
|
||||
|
@ -341,7 +341,7 @@ packages:
|
|||
description:
|
||||
name: image_picker
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
image_picker_android:
|
||||
|
@ -349,7 +349,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_android
|
||||
sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.8.12+23"
|
||||
image_picker_for_web:
|
||||
|
@ -357,7 +357,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
image_picker_ios:
|
||||
|
@ -365,7 +365,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.8.12+2"
|
||||
image_picker_linux:
|
||||
|
@ -373,7 +373,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_macos:
|
||||
|
@ -381,7 +381,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
image_picker_platform_interface:
|
||||
|
@ -389,7 +389,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.10.1"
|
||||
image_picker_windows:
|
||||
|
@ -397,7 +397,7 @@ packages:
|
|||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
intl:
|
||||
|
@ -405,7 +405,7 @@ packages:
|
|||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
js:
|
||||
|
@ -413,23 +413,23 @@ packages:
|
|||
description:
|
||||
name: js
|
||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
version: "10.0.8"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
|
@ -437,7 +437,7 @@ packages:
|
|||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
|
@ -445,7 +445,7 @@ packages:
|
|||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
list_counter:
|
||||
|
@ -453,7 +453,7 @@ packages:
|
|||
description:
|
||||
name: list_counter
|
||||
sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
matcher:
|
||||
|
@ -461,7 +461,7 @@ packages:
|
|||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
|
@ -469,7 +469,7 @@ packages:
|
|||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
|
@ -477,7 +477,7 @@ packages:
|
|||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
|
@ -485,7 +485,7 @@ packages:
|
|||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobile_scanner:
|
||||
|
@ -493,7 +493,7 @@ packages:
|
|||
description:
|
||||
name: mobile_scanner
|
||||
sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
nested:
|
||||
|
@ -501,7 +501,7 @@ packages:
|
|||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
nm:
|
||||
|
@ -509,7 +509,7 @@ packages:
|
|||
description:
|
||||
name: nm
|
||||
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
package_info_plus:
|
||||
|
@ -517,7 +517,7 @@ packages:
|
|||
description:
|
||||
name: package_info_plus
|
||||
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
package_info_plus_platform_interface:
|
||||
|
@ -525,7 +525,7 @@ packages:
|
|||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
path:
|
||||
|
@ -533,7 +533,7 @@ packages:
|
|||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
|
@ -541,7 +541,7 @@ packages:
|
|||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
|
@ -549,7 +549,7 @@ packages:
|
|||
description:
|
||||
name: path_provider_android
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.17"
|
||||
path_provider_foundation:
|
||||
|
@ -557,7 +557,7 @@ packages:
|
|||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
|
@ -565,7 +565,7 @@ packages:
|
|||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
|
@ -573,7 +573,7 @@ packages:
|
|||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
|
@ -581,7 +581,7 @@ packages:
|
|||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
|
@ -589,7 +589,7 @@ packages:
|
|||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
photo_manager:
|
||||
|
@ -597,7 +597,7 @@ packages:
|
|||
description:
|
||||
name: photo_manager
|
||||
sha256: a0d9a7a9bc35eda02d33766412bde6d883a8b0acb86bbe37dac5f691a0894e8a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.7.1"
|
||||
photo_manager_image_provider:
|
||||
|
@ -605,7 +605,7 @@ packages:
|
|||
description:
|
||||
name: photo_manager_image_provider
|
||||
sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
photo_view:
|
||||
|
@ -613,7 +613,7 @@ packages:
|
|||
description:
|
||||
name: photo_view
|
||||
sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.15.0"
|
||||
platform:
|
||||
|
@ -621,7 +621,7 @@ packages:
|
|||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
|
@ -629,7 +629,7 @@ packages:
|
|||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
|
@ -645,7 +645,7 @@ packages:
|
|||
description:
|
||||
name: provider
|
||||
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
shared_preferences:
|
||||
|
@ -653,7 +653,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
shared_preferences_android:
|
||||
|
@ -661,7 +661,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.10"
|
||||
shared_preferences_foundation:
|
||||
|
@ -669,7 +669,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
|
@ -677,7 +677,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
|
@ -685,7 +685,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_web:
|
||||
|
@ -693,7 +693,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
|
@ -701,7 +701,7 @@ packages:
|
|||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
simple_gesture_detector:
|
||||
|
@ -709,7 +709,7 @@ packages:
|
|||
description:
|
||||
name: simple_gesture_detector
|
||||
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
sky_engine:
|
||||
|
@ -722,7 +722,7 @@ packages:
|
|||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
stack_trace:
|
||||
|
@ -730,7 +730,7 @@ packages:
|
|||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
|
@ -738,7 +738,7 @@ packages:
|
|||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
|
@ -754,7 +754,7 @@ packages:
|
|||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
table_calendar:
|
||||
|
@ -762,7 +762,7 @@ packages:
|
|||
description:
|
||||
name: table_calendar
|
||||
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
term_glyph:
|
||||
|
@ -770,7 +770,7 @@ packages:
|
|||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
|
@ -778,7 +778,7 @@ packages:
|
|||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
typed_data:
|
||||
|
@ -786,7 +786,7 @@ packages:
|
|||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_math:
|
||||
|
@ -794,7 +794,7 @@ packages:
|
|||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
video_player:
|
||||
|
@ -802,7 +802,7 @@ packages:
|
|||
description:
|
||||
name: video_player
|
||||
sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
video_player_android:
|
||||
|
@ -810,7 +810,7 @@ packages:
|
|||
description:
|
||||
name: video_player_android
|
||||
sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.8.7"
|
||||
video_player_avfoundation:
|
||||
|
@ -818,7 +818,7 @@ packages:
|
|||
description:
|
||||
name: video_player_avfoundation
|
||||
sha256: "9fedd55023249f3a02738c195c906b4e530956191febf0838e37d0dac912f953"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.8.0"
|
||||
video_player_platform_interface:
|
||||
|
@ -826,7 +826,7 @@ packages:
|
|||
description:
|
||||
name: video_player_platform_interface
|
||||
sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.4.0"
|
||||
video_player_web:
|
||||
|
@ -834,7 +834,7 @@ packages:
|
|||
description:
|
||||
name: video_player_web
|
||||
sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
visibility_detector:
|
||||
|
@ -842,23 +842,23 @@ packages:
|
|||
description:
|
||||
name: visibility_detector
|
||||
sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.4.0+2"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
version: "14.3.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
webview_flutter:
|
||||
|
@ -866,7 +866,7 @@ packages:
|
|||
description:
|
||||
name: webview_flutter
|
||||
sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.13.0"
|
||||
webview_flutter_android:
|
||||
|
@ -874,7 +874,7 @@ packages:
|
|||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: "769f34fc9855f7d7789b786b79b7c37a60e92ff08f71e3a429208d7f5b81d944"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
webview_flutter_platform_interface:
|
||||
|
@ -882,7 +882,7 @@ packages:
|
|||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: f0dc2dc3a2b1e3a6abdd6801b9355ebfeb3b8f6cde6b9dc7c9235909c4a1f147
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
webview_flutter_wkwebview:
|
||||
|
@ -890,7 +890,7 @@ packages:
|
|||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "71523b9048cf510cfa1fd4e0a3fa5e476a66e0884d5df51d59d5023dba237107"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.22.1"
|
||||
wechat_assets_picker:
|
||||
|
@ -898,7 +898,7 @@ packages:
|
|||
description:
|
||||
name: wechat_assets_picker
|
||||
sha256: cafe3d32564ed3cacf9822f251941f7b44fe9885c17c8de4fca7e939a459e1ef
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "9.5.1"
|
||||
wechat_picker_library:
|
||||
|
@ -906,23 +906,23 @@ packages:
|
|||
description:
|
||||
name: wechat_picker_library
|
||||
sha256: a42e09cb85b15fc9410f6a69671371cc60aa99c4a1f7967f6593a7f665f6f47a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.14.0"
|
||||
version: "5.13.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
|
@ -930,9 +930,9 @@ packages:
|
|||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
|
Loading…
Reference in New Issue