Compare commits

..

No commits in common. "a5a8a7ef34515c0fde1baa442156ca743958b192" and "9c10669cf8b5b1846e8fc4e79171cf241417df26" have entirely different histories.

36 changed files with 1377 additions and 2211 deletions

View File

@ -36,27 +36,27 @@ android {
versionName = flutter.versionName versionName = flutter.versionName
} }
// ✅ 添加 release 签名配置 // // ✅ 添加 release 签名配置
signingConfigs { // signingConfigs {
create("release") { // create("release") {
storeFile = file(keystoreProperties["storeFile"] as String) // storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String // storePassword = keystoreProperties["storePassword"] as String
keyAlias = keystoreProperties["keyAlias"] as String // keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String // keyPassword = keystoreProperties["keyPassword"] as String
} // }
} // }
//
buildTypes { // buildTypes {
release { // release {
// ✅ 替换成 release 签名 // // ✅ 替换成 release 签名
signingConfig = signingConfigs.getByName("release") // signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false // isMinifyEnabled = false
isShrinkResources = false // isShrinkResources = false
} // }
debug { // debug {
signingConfig = signingConfigs.getByName("debug") // signingConfig = signingConfigs.getByName("debug")
} // }
} // }
} }
flutter { flutter {

View File

@ -227,7 +227,7 @@
const x = [e.latlng.lng, e.latlng.lat]; const x = [e.latlng.lng, e.latlng.lat];
let inside = (GSON_LON_LAT_BD09 && GSON_LON_LAT_BD09.length > 0) ? isPointInPolygon(x, GSON_LON_LAT_BD09) : true; let inside = (GSON_LON_LAT_BD09 && GSON_LON_LAT_BD09.length > 0) ? isPointInPolygon(x, GSON_LON_LAT_BD09) : true;
if (!inside) { if (!inside) {
//alert("当前选择点位不在区域中!"); alert("当前选择点位不在区域中!");
notifyHost({type:'point_selected', ok:false, reason:'out_of_polygon', lng:e.latlng.lng, lat:e.latlng.lat}); notifyHost({type:'point_selected', ok:false, reason:'out_of_polygon', lng:e.latlng.lng, lat:e.latlng.lat});
} else { } else {
map.addOverlay(marker); map.addOverlay(marker);

View File

@ -47,8 +47,6 @@ PODS:
- FlutterMacOS - FlutterMacOS
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- video_compress (0.3.0):
- Flutter
- video_player_avfoundation (0.0.1): - video_player_avfoundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@ -77,7 +75,6 @@ DEPENDENCIES:
- photo_manager (from `.symlinks/plugins/photo_manager/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_compress (from `.symlinks/plugins/video_compress/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
@ -123,8 +120,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
video_compress:
:path: ".symlinks/plugins/video_compress/ios"
video_player_avfoundation: video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin" :path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus: wakelock_plus:
@ -152,7 +147,6 @@ SPEC CHECKSUMS:
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62 photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_compress: f2133a07762889d67f0711ac831faa26f956980e
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2

View File

@ -50,14 +50,14 @@ class _BigVideoViewerState extends State<BigVideoViewer> {
appBar: MyAppbar( appBar: MyAppbar(
backgroundColor: Colors.transparent, title: '', backgroundColor: Colors.transparent, title: '',
), ),
body: SafeArea(child: Center( body: Center(
child: VideoPlayerWidget( child: VideoPlayerWidget(
allowSeek: false, allowSeek: false,
controller: _videoController, controller: _videoController,
coverUrl:"", coverUrl:"",
aspectRatio: _videoController?.value.aspectRatio ?? 16/9, aspectRatio: _videoController?.value.aspectRatio ?? 16/9,
), ),
),) ),
); );
} }
} }

View File

@ -1,9 +1,8 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
/// / /// Chewie VideoPlayerPopup
class VideoPlayerPopup extends StatefulWidget { class VideoPlayerPopup extends StatefulWidget {
final String videoUrl; final String videoUrl;
const VideoPlayerPopup({Key? key, required this.videoUrl}) : super(key: key); const VideoPlayerPopup({Key? key, required this.videoUrl}) : super(key: key);
@ -15,42 +14,15 @@ class VideoPlayerPopup extends StatefulWidget {
class _VideoPlayerPopupState extends State<VideoPlayerPopup> { class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
late VideoPlayerController _videoController; late VideoPlayerController _videoController;
ChewieController? _chewieController; ChewieController? _chewieController;
bool _isNetwork = false;
bool _initializing = true;
String? _error;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initController(); _videoController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl))
} ..initialize().then((_) {
setState(() {});
});
Future<void> _initController() async {
try {
final uri = Uri.tryParse(widget.videoUrl);
final scheme = uri?.scheme?.toLowerCase() ?? '';
// http/https/rtsp/rtmp
_isNetwork = (scheme == 'http' || scheme == 'https' || scheme == 'rtsp' || scheme == 'rtmp');
if (_isNetwork) {
//
_videoController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
} else {
// file://
if (scheme == 'file' && uri != null) {
_videoController = VideoPlayerController.file(File(uri.toFilePath()));
} else {
// /storage/...
_videoController = VideoPlayerController.file(File(widget.videoUrl));
}
}
// VideoPlayerController
await _videoController.initialize();
// ChewieController aspectRatio
_chewieController?.dispose();
_chewieController = ChewieController( _chewieController = ChewieController(
videoPlayerController: _videoController, videoPlayerController: _videoController,
autoPlay: true, autoPlay: true,
@ -60,39 +32,9 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
allowPlaybackSpeedChanging: true, allowPlaybackSpeedChanging: true,
allowMuting: true, allowMuting: true,
showControlsOnInitialize: true, showControlsOnInitialize: true,
// materialProgressColors: ChewieProgressColors(playedColor: Colors.blue,backgroundColor: Colors.white, handleColor:Colors.blue,bufferedColor:Colors.red),
materialProgressColors: ChewieProgressColors( aspectRatio: _videoController.value.aspectRatio,
playedColor: Colors.blue,
backgroundColor: Colors.white,
handleColor: Colors.blue,
bufferedColor: Colors.red,
),
aspectRatio: _videoController.value.aspectRatio > 0
? _videoController.value.aspectRatio
: 16 / 9,
errorBuilder: (context, errorMessage) {
return Center(
child: Text(
errorMessage ?? '视频播放错误',
style: const TextStyle(color: Colors.white),
),
); );
},
);
if (!mounted) return;
setState(() {
_initializing = false;
});
} catch (e, st) {
//
debugPrint('Video init error: $e\n$st');
if (!mounted) return;
setState(() {
_initializing = false;
_error = e.toString();
});
}
} }
@override @override
@ -118,10 +60,12 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
), ),
child: Stack( child: Stack(
children: [ children: [
// / //
Positioned.fill( if (_chewieController != null &&
child: _buildPlayerBody(), _videoController.value.isInitialized)
), Chewie(controller: _chewieController!)
else
const Center(child: CircularProgressIndicator()),
// //
Positioned( Positioned(
@ -129,9 +73,7 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
right: 4, right: 4,
child: IconButton( child: IconButton(
icon: const Icon(Icons.close, color: Colors.white), icon: const Icon(Icons.close, color: Colors.white),
onPressed: () { onPressed: () => Navigator.of(context).pop(),
Navigator.of(context).pop();
},
), ),
), ),
], ],
@ -140,28 +82,4 @@ class _VideoPlayerPopupState extends State<VideoPlayerPopup> {
), ),
); );
} }
Widget _buildPlayerBody() {
if (_initializing) {
return const Center(child: CircularProgressIndicator());
}
if (_error != null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'播放失败:$_error',
style: const TextStyle(color: Colors.white),
),
),
);
}
if (_chewieController != null && _videoController.value.isInitialized) {
return Chewie(controller: _chewieController!);
}
return const Center(child: Text('无法播放视频', style: TextStyle(color: Colors.white)));
}
} }

View File

@ -1,12 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:qhd_prevention/tools/VideoConverter.dart';
import 'package:video_compress/video_compress.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
import 'package:path/path.dart' as p;
import 'ItemWidgetFactory.dart'; import 'ItemWidgetFactory.dart';
/// ///
@ -25,6 +22,7 @@ class MediaPickerRow extends StatefulWidget {
final bool isEdit; // final bool isEdit; //
final bool isCamera; // final bool isCamera; //
const MediaPickerRow({ const MediaPickerRow({
Key? key, Key? key,
this.maxCount = 4, this.maxCount = 4,
@ -45,7 +43,6 @@ class MediaPickerRow extends StatefulWidget {
class _MediaPickerGridState extends State<MediaPickerRow> { class _MediaPickerGridState extends State<MediaPickerRow> {
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
late List<String> _mediaPaths; late List<String> _mediaPaths;
bool _isProcessing = false; // loading
@override @override
void initState() { void initState() {
@ -59,81 +56,16 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
); );
}); });
} }
// mov/avi
Future<void> _handlePickedPath(String path) async {
if (!mounted) return;
if (path.isEmpty) return;
try {
String finalPath = path;
// mp4 video_compress
if (widget.mediaType == MediaType.video) {
final ext = p.extension(path).toLowerCase();
if (ext != '.mp4') {
setState(() => _isProcessing = true);
try {
final info = await VideoCompress.compressVideo(
path,
quality: VideoQuality.MediumQuality,
deleteOrigin: false,
);
if (info != null && info.file != null) {
finalPath = info.file!.path;
debugPrint('✅ 转换完成: $path -> $finalPath');
} else {
throw Exception("转码失败: 返回空文件");
}
} catch (e) {
debugPrint('❌ 视频转码失败: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('视频转码失败: ${e.toString()}')),
);
}
return;
} finally {
if (mounted) setState(() => _isProcessing = false);
}
}
}
//
if (_mediaPaths.length < widget.maxCount) {
setState(() => _mediaPaths.add(finalPath));
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onMediaAdded?.call(finalPath);
}
} catch (e) {
debugPrint('处理选中媒体失败: $e');
}
}
Future<void> _cameraAction() async { Future<void> _cameraAction() async {
if (!widget.isEdit || _mediaPaths.length >= widget.maxCount) return;
try {
if (widget.mediaType == MediaType.image) {
XFile? picked = await _picker.pickImage(source: ImageSource.camera); XFile? picked = await _picker.pickImage(source: ImageSource.camera);
if (picked != null) { if (picked != null) {
final path = picked.path; final path = picked.path;
setState(() => _mediaPaths.add(path)); setState(() => _mediaPaths.add(path));
widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onMediaAdded?.call(path); widget.onMediaAdded?.call(path);
} }
} else {
// video from camera
XFile? picked = await _picker.pickVideo(source: ImageSource.camera);
if (picked != null) {
await _handlePickedPath(picked.path);
} }
}
} catch (e) {
debugPrint('拍摄失败: $e');
}
}
Future<void> _showPickerOptions() async { Future<void> _showPickerOptions() async {
if (!widget.isEdit) return; // if (!widget.isEdit) return; //
@ -146,9 +78,13 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
ListTile( ListTile(
titleAlignment: ListTileTitleAlignment.center, titleAlignment: ListTileTitleAlignment.center,
leading: Icon( leading: Icon(
widget.mediaType == MediaType.image ? Icons.camera_alt : Icons.videocam, widget.mediaType == MediaType.image
? Icons.camera_alt
: Icons.videocam,
),
title: Text(
widget.mediaType == MediaType.image ? '拍照' : '拍摄视频',
), ),
title: Text(widget.mediaType == MediaType.image ? '拍照' : '拍摄视频'),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
_pickCamera(); _pickCamera();
@ -157,9 +93,15 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
ListTile( ListTile(
titleAlignment: ListTileTitleAlignment.center, titleAlignment: ListTileTitleAlignment.center,
leading: Icon( leading: Icon(
widget.mediaType == MediaType.image ? Icons.photo_library : Icons.video_library, widget.mediaType == MediaType.image
? Icons.photo_library
: Icons.video_library,
),
title: Text(
widget.mediaType == MediaType.image
? '从相册选择'
: '从相册选择视频',
), ),
title: Text(widget.mediaType == MediaType.image ? '从相册选择' : '从相册选择视频'),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
_pickGallery(); _pickGallery();
@ -184,18 +126,15 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
XFile? picked; XFile? picked;
if (widget.mediaType == MediaType.image) { if (widget.mediaType == MediaType.image) {
picked = await _picker.pickImage(source: ImageSource.camera); picked = await _picker.pickImage(source: ImageSource.camera);
} else {
picked = await _picker.pickVideo(source: ImageSource.camera);
}
if (picked != null) { if (picked != null) {
final path = picked.path; final path = picked.path;
setState(() => _mediaPaths.add(path)); setState(() => _mediaPaths.add(path));
widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onMediaAdded?.call(path); widget.onMediaAdded?.call(path);
} }
} else {
picked = await _picker.pickVideo(source: ImageSource.camera);
if (picked != null) {
await _handlePickedPath(picked.path);
}
}
} catch (e) { } catch (e) {
debugPrint('拍摄失败: $e'); debugPrint('拍摄失败: $e');
} }
@ -217,7 +156,9 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
final List<AssetEntity>? assets = await AssetPicker.pickAssets( final List<AssetEntity>? assets = await AssetPicker.pickAssets(
context, context,
pickerConfig: AssetPickerConfig( pickerConfig: AssetPickerConfig(
requestType: widget.mediaType == MediaType.image ? RequestType.image : RequestType.video, requestType: widget.mediaType == MediaType.image
? RequestType.image
: RequestType.video,
maxAssets: remaining, maxAssets: remaining,
gridCount: 4, gridCount: 4,
), ),
@ -228,8 +169,8 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
final file = await asset.file; final file = await asset.file;
if (file != null) { if (file != null) {
final path = file.path; final path = file.path;
// _mediaPaths.add(path);
await _handlePickedPath(path); widget.onMediaAdded?.call(path);
} }
} }
setState(() {}); setState(() {});
@ -254,9 +195,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount; final showAddButton = widget.isEdit && _mediaPaths.length < widget.maxCount;
final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0); final itemCount = _mediaPaths.length + (showAddButton ? 1 : 0);
return Stack( return GridView.builder(
children: [
GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
@ -264,6 +203,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
crossAxisSpacing: 8, crossAxisSpacing: 8,
mainAxisSpacing: 8, mainAxisSpacing: 8,
childAspectRatio: 1, childAspectRatio: 1,
// mainAxisExtent: 80,
), ),
itemCount: itemCount, itemCount: itemCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -324,19 +264,6 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
}, },
),
// / loading
if (_isProcessing)
Positioned.fill(
child: Container(
color: Colors.transparent,
child: const Center(
child: CircularProgressIndicator(),
),
),
),
],
); );
} }
} }
@ -360,6 +287,7 @@ class RepairedPhotoSection extends StatefulWidget {
final bool isEdit; // final bool isEdit; //
final bool isCamera; // final bool isCamera; //
const RepairedPhotoSection({ const RepairedPhotoSection({
Key? key, Key? key,
this.maxCount = 4, this.maxCount = 4,

View File

@ -4,21 +4,17 @@ import 'package:flutter/material.dart';
/// ///
/// DateTime? picked = await BottomDateTimePicker.showDate( /// DateTime? picked = await BottomDateTimePicker.showDate(
/// context, /// context,
/// mode: BottomPickerMode.date, // BottomPickerMode.dateTime /// allowFuture: true, //
/// allowFuture: true, /// minTimeStr: '2025-08-20 08:30', //
/// minTimeStr: '2025-08-20 08:30',
/// ); /// );
/// if (picked != null) { /// if (picked != null) {
/// print('用户选择的时间:$picked'); /// print('用户选择的时间:$picked');
/// } /// }
enum BottomPickerMode { dateTime, date }
class BottomDateTimePicker { class BottomDateTimePicker {
static Future<DateTime?> showDate( static Future<DateTime?> showDate(
BuildContext context, { BuildContext context, {
bool allowFuture = false, bool allowFuture = false,
String? minTimeStr, // 'yyyy-MM-dd HH:mm' String? minTimeStr, // 'yyyy-MM-dd HH:mm'
BottomPickerMode mode = BottomPickerMode.dateTime,
}) { }) {
return showModalBottomSheet<DateTime>( return showModalBottomSheet<DateTime>(
context: context, context: context,
@ -30,22 +26,19 @@ class BottomDateTimePicker {
builder: (_) => _InlineDateTimePickerContent( builder: (_) => _InlineDateTimePickerContent(
allowFuture: allowFuture, allowFuture: allowFuture,
minTimeStr: minTimeStr, minTimeStr: minTimeStr,
mode: mode,
), ),
); );
} }
} }
class _InlineDateTimePickerContent extends StatefulWidget { class _InlineDateTimePickerContent extends StatefulWidget {
final bool allowFuture; final bool allowFuture; //
final String? minTimeStr; final String? minTimeStr; // 'yyyy-MM-dd HH:mm'
final BottomPickerMode mode;
const _InlineDateTimePickerContent({ const _InlineDateTimePickerContent({
Key? key, Key? key,
this.allowFuture = false, this.allowFuture = false,
this.minTimeStr, this.minTimeStr,
this.mode = BottomPickerMode.dateTime,
}) : super(key: key); }) : super(key: key);
@override @override
@ -87,18 +80,13 @@ class _InlineDateTimePickerContentState
// minTimeStr // minTimeStr
_minTime = _parseMinTime(widget.minTimeStr); _minTime = _parseMinTime(widget.minTimeStr);
// now _minTime // DateTime.now() _minTime
final now = DateTime.now(); final now = DateTime.now();
DateTime initial = now; DateTime initial = now;
if (_minTime != null && _minTime!.isAfter(initial)) { if (_minTime != null && _minTime!.isAfter(initial)) {
initial = _minTime!; initial = _minTime!;
} }
// date
if (widget.mode == BottomPickerMode.date) {
initial = DateTime(initial.year, initial.month, initial.day);
}
selectedYear = initial.year; selectedYear = initial.year;
selectedMonth = initial.month; selectedMonth = initial.month;
selectedDay = initial.day; selectedDay = initial.day;
@ -111,16 +99,12 @@ class _InlineDateTimePickerContentState
// controllers // controllers
yearCtrl = FixedExtentScrollController( yearCtrl = FixedExtentScrollController(
initialItem: years.indexOf(selectedYear).clamp(0, years.length - 1)); initialItem: years.indexOf(selectedYear).clamp(0, years.length - 1));
monthCtrl = FixedExtentScrollController( monthCtrl = FixedExtentScrollController(initialItem: (selectedMonth - 1).clamp(0, months.length - 1));
initialItem: (selectedMonth - 1).clamp(0, months.length - 1)); dayCtrl = FixedExtentScrollController(initialItem: (selectedDay - 1).clamp(0, days.length - 1));
dayCtrl = FixedExtentScrollController( hourCtrl = FixedExtentScrollController(initialItem: selectedHour.clamp(0, hours.length - 1));
initialItem: (selectedDay - 1).clamp(0, days.length - 1)); minuteCtrl = FixedExtentScrollController(initialItem: selectedMinute.clamp(0, minutes.length - 1));
hourCtrl = FixedExtentScrollController(
initialItem: selectedHour.clamp(0, hours.length - 1));
minuteCtrl = FixedExtentScrollController(
initialItem: selectedMinute.clamp(0, minutes.length - 1));
// minTime // minTime
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_enforceConstraintsAndUpdateControllers(); _enforceConstraintsAndUpdateControllers();
}); });
@ -138,8 +122,7 @@ class _InlineDateTimePickerContentState
try { try {
final trimmed = s.trim(); final trimmed = s.trim();
final parts = trimmed.split(' '); final parts = trimmed.split(' ');
final dateParts = final dateParts = parts[0].split('-').map((e) => int.parse(e)).toList();
parts[0].split('-').map((e) => int.parse(e)).toList();
final timeParts = (parts.length > 1) final timeParts = (parts.length > 1)
? parts[1].split(':').map((e) => int.parse(e)).toList() ? parts[1].split(':').map((e) => int.parse(e)).toList()
: [0, 0]; : [0, 0];
@ -150,6 +133,7 @@ class _InlineDateTimePickerContentState
final minute = (timeParts.length > 1) ? timeParts[1] : 0; final minute = (timeParts.length > 1) ? timeParts[1] : 0;
return DateTime(year, month, day, hour, minute); return DateTime(year, month, day, hour, minute);
} catch (e) { } catch (e) {
//
debugPrint('parseMinTime failed for "$s": $e'); debugPrint('parseMinTime failed for "$s": $e');
return null; return null;
} }
@ -169,72 +153,49 @@ class _InlineDateTimePickerContentState
}); });
} }
// //
// 1) _minTime _minTime
// 2) _minTime allowFuture now
// _minTime allowFuture _minTime _minTime
void _enforceConstraintsAndUpdateControllers() { void _enforceConstraintsAndUpdateControllers() {
final now = DateTime.now(); final picked = DateTime(selectedYear, selectedMonth, selectedDay, selectedHour, selectedMinute);
final isDateOnly = widget.mode == BottomPickerMode.date;
final DateTime picked = isDateOnly // 1)
? DateTime(selectedYear, selectedMonth, selectedDay) if (_minTime != null && picked.isBefore(_minTime!)) {
: DateTime( final m = _minTime!;
selectedYear, selectedMonth, selectedDay, selectedHour, selectedMinute); selectedYear = m.year;
selectedMonth = m.month;
selectedDay = m.day;
selectedHour = m.hour;
selectedMinute = m.minute;
// _minTime date //
if (_minTime != null) {
final DateTime minRef = isDateOnly
? DateTime(_minTime!.year, _minTime!.month, _minTime!.day)
: _minTime!;
if (picked.isBefore(minRef)) {
// minRef
selectedYear = minRef.year;
selectedMonth = minRef.month;
selectedDay = minRef.day;
if (!isDateOnly) {
selectedHour = minRef.hour;
selectedMinute = minRef.minute;
} else {
selectedHour = 0;
selectedMinute = 0;
}
//
_updateDays(jumpDay: false); _updateDays(jumpDay: false);
yearCtrl.jumpToItem(years.indexOf(selectedYear)); yearCtrl.jumpToItem(years.indexOf(selectedYear));
monthCtrl.jumpToItem(selectedMonth - 1); monthCtrl.jumpToItem(selectedMonth - 1);
dayCtrl.jumpToItem(selectedDay - 1); dayCtrl.jumpToItem(selectedDay - 1);
if (!isDateOnly) {
hourCtrl.jumpToItem(selectedHour); hourCtrl.jumpToItem(selectedHour);
minuteCtrl.jumpToItem(selectedMinute); minuteCtrl.jumpToItem(selectedMinute);
}
return; return;
} }
}
// allowFuture == false // 2) allowFuture == false minTime minTime <= now
if (!widget.allowFuture) { if (!widget.allowFuture) {
final DateTime nowRef = isDateOnly final now = DateTime.now();
? DateTime(now.year, now.month, now.day) // minTime nowminTime
: now; if (picked.isAfter(now)) {
if (picked.isAfter(nowRef)) { selectedYear = now.year;
selectedYear = nowRef.year; selectedMonth = now.month;
selectedMonth = nowRef.month; selectedDay = now.day;
selectedDay = nowRef.day; selectedHour = now.hour;
if (!isDateOnly) { selectedMinute = now.minute;
selectedHour = nowRef.hour;
selectedMinute = nowRef.minute;
} else {
selectedHour = 0;
selectedMinute = 0;
}
_updateDays(jumpDay: false); _updateDays(jumpDay: false);
yearCtrl.jumpToItem(years.indexOf(selectedYear)); yearCtrl.jumpToItem(years.indexOf(selectedYear));
monthCtrl.jumpToItem(selectedMonth - 1); monthCtrl.jumpToItem(selectedMonth - 1);
dayCtrl.jumpToItem(selectedDay - 1); dayCtrl.jumpToItem(selectedDay - 1);
if (!isDateOnly) {
hourCtrl.jumpToItem(selectedHour); hourCtrl.jumpToItem(selectedHour);
minuteCtrl.jumpToItem(selectedMinute); minuteCtrl.jumpToItem(selectedMinute);
}
return; return;
} }
} }
@ -252,9 +213,8 @@ class _InlineDateTimePickerContentState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDateOnly = widget.mode == BottomPickerMode.date;
return SizedBox( return SizedBox(
height: isDateOnly ? 280 : 330, height: 330,
child: Column( child: Column(
children: [ children: [
// //
@ -269,9 +229,7 @@ class _InlineDateTimePickerContentState
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
final result = isDateOnly final result = DateTime(
? DateTime(selectedYear, selectedMonth, selectedDay)
: DateTime(
selectedYear, selectedYear,
selectedMonth, selectedMonth,
selectedDay, selectedDay,
@ -287,7 +245,7 @@ class _InlineDateTimePickerContentState
), ),
const Divider(height: 1), const Divider(height: 1),
// date //
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
@ -323,6 +281,7 @@ class _InlineDateTimePickerContentState
items: days.map((e) => e.toString().padLeft(2, '0')).toList(), items: days.map((e) => e.toString().padLeft(2, '0')).toList(),
onSelected: (idx) { onSelected: (idx) {
setState(() { setState(() {
// idx days
final safeIdx = idx.clamp(0, days.length - 1); final safeIdx = idx.clamp(0, days.length - 1);
selectedDay = days[safeIdx]; selectedDay = days[safeIdx];
_enforceConstraintsAndUpdateControllers(); _enforceConstraintsAndUpdateControllers();
@ -330,8 +289,7 @@ class _InlineDateTimePickerContentState
}, },
), ),
// dateOnly //
if (!isDateOnly)
_buildPicker( _buildPicker(
controller: hourCtrl, controller: hourCtrl,
items: hours.map((e) => e.toString().padLeft(2, '0')).toList(), items: hours.map((e) => e.toString().padLeft(2, '0')).toList(),
@ -343,7 +301,7 @@ class _InlineDateTimePickerContentState
}, },
), ),
if (!isDateOnly) //
_buildPicker( _buildPicker(
controller: minuteCtrl, controller: minuteCtrl,
items: minutes.map((e) => e.toString().padLeft(2, '0')).toList(), items: minutes.map((e) => e.toString().padLeft(2, '0')).toList(),

View File

@ -3,7 +3,6 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:qhd_prevention/tools/tools.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
class VideoPlayerWidget extends StatefulWidget { class VideoPlayerWidget extends StatefulWidget {
@ -29,121 +28,57 @@ class VideoPlayerWidget extends StatefulWidget {
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> { class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
bool _visibleControls = true; bool _visibleControls = true;
Timer? _hideTimer; Timer? _hideTimer;
Timer? _positionTimer; late Timer _positionTimer;
Duration _currentPosition = Duration.zero; Duration _currentPosition = Duration.zero;
Duration _totalDuration = Duration.zero; Duration _totalDuration = Duration.zero;
bool _isPlaying = false; bool _isPlaying = false;
final ValueNotifier<double> _sliderValue = ValueNotifier<double>(0.0); final ValueNotifier<double> _sliderValue = ValueNotifier(0.0);
// Helpers to avoid accessing controller after it's disposed
bool get _hasController => widget.controller != null;
/// Safe check whether controller is initialized without throwing.
bool _controllerInitializedSafe() {
try {
final c = widget.controller;
if (c == null) return false;
return c.value.isInitialized;
} catch (e) {
return false;
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// start hide timer immediately
_startHideTimer(); _startHideTimer();
// start position timer only if controller exists and initialized _startPositionTimer();
_maybeStartPositionTimer(); if (widget.controller != null) {
// add listener if controller present widget.controller!.addListener(_controllerListener);
_addControllerListenerSafely(); if (widget.controller!.value.isInitialized) {
// if controller already initialized, fetch values _updateControllerValues();
if (_controllerInitializedSafe()) { }
_updateControllerValuesSafe();
} }
} }
@override @override
void didUpdateWidget(covariant VideoPlayerWidget oldWidget) { void didUpdateWidget(covariant VideoPlayerWidget oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
// controller changed -> update listeners and timers oldWidget.controller?.removeListener(_controllerListener);
if (oldWidget.controller != widget.controller) {
_removeControllerListenerSafely(oldWidget.controller);
_addControllerListenerSafely();
_restartPositionTimer();
_updateControllerValuesSafe();
}
}
void _addControllerListenerSafely() {
try {
widget.controller?.addListener(_controllerListener); widget.controller?.addListener(_controllerListener);
} catch (_) { _updateControllerValues();
// ignore if controller already disposed
}
}
void _removeControllerListenerSafely([VideoPlayerController? ctrl]) {
final c = ctrl ?? widget.controller;
if (c == null) return;
try {
c.removeListener(_controllerListener);
} catch (_) {
// ignore
} }
} }
void _controllerListener() { void _controllerListener() {
if (!mounted) return; if (!mounted) return;
_updateControllerValuesSafe(); _updateControllerValues();
} }
void _updateControllerValuesSafe() { void _updateControllerValues() {
final c = widget.controller; final c = widget.controller!;
if (c == null) {
// clear values
if (mounted) {
setState(() { setState(() {
_isPlaying = false; _isPlaying = c.value.isPlaying;
_totalDuration = Duration.zero; _totalDuration = c.value.duration;
_currentPosition = Duration.zero; _currentPosition = c.value.position;
_sliderValue.value = 0; _sliderValue.value = _currentPosition.inMilliseconds.toDouble();
}); });
} }
return;
}
try { @override
final value = c.value; void dispose() {
if (!value.isInitialized) { _hideTimer?.cancel();
if (mounted) { _positionTimer.cancel();
setState(() { _sliderValue.dispose();
_isPlaying = false; widget.controller?.removeListener(_controllerListener);
_totalDuration = Duration.zero; super.dispose();
_currentPosition = Duration.zero;
_sliderValue.value = 0;
});
}
return;
}
final pos = value.position;
final dur = value.duration;
final playing = value.isPlaying;
if (mounted) {
setState(() {
_isPlaying = playing;
_totalDuration = dur;
_currentPosition = pos;
_sliderValue.value = pos.inMilliseconds.toDouble();
});
}
} catch (e) {
// If controller was disposed between checks, ignore
}
} }
void _startHideTimer() { void _startHideTimer() {
@ -153,80 +88,44 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
}); });
} }
void _maybeStartPositionTimer() { void _startPositionTimer() {
if (_positionTimer != null && _positionTimer!.isActive) return; _positionTimer = Timer.periodic(const Duration(milliseconds: 200), (_) {
// only start if controller exists if (!mounted ||
if (!_hasController) return; widget.controller == null ||
_positionTimer = Timer.periodic(const Duration(milliseconds: 300), (_) { !widget.controller!.value.isInitialized) return;
if (!mounted) return;
if (!_controllerInitializedSafe()) return;
try {
final c = widget.controller!;
final pos = c.value.position;
final dur = c.value.duration;
final playing = c.value.isPlaying;
setState(() { setState(() {
_currentPosition = pos; final c = widget.controller!;
_totalDuration = dur; _currentPosition = c.value.position;
_isPlaying = playing; _totalDuration = c.value.duration;
_sliderValue.value = pos.inMilliseconds.toDouble(); _isPlaying = c.value.isPlaying;
_sliderValue.value = _currentPosition.inMilliseconds.toDouble();
}); });
} catch (_) {
// ignore if controller disposed mid-tick
}
}); });
} }
void _restartPositionTimer() {
_positionTimer?.cancel();
_maybeStartPositionTimer();
}
void _stopPositionTimer() {
try {
_positionTimer?.cancel();
} catch (_) {}
_positionTimer = null;
}
void _toggleControls() { void _toggleControls() {
setState(() => _visibleControls = !_visibleControls); setState(() => _visibleControls = !_visibleControls);
if (_visibleControls) if (_visibleControls) _startHideTimer();
_startHideTimer(); else _hideTimer?.cancel();
else
_hideTimer?.cancel();
} }
void _togglePlayPause() { void _togglePlayPause() {
final c = widget.controller; if (widget.controller == null) return;
if (c == null) return; setState(() => _isPlaying = !_isPlaying);
try { if (_isPlaying) widget.controller!.play();
if (_isPlaying) { else widget.controller!.pause();
c.pause();
setState(() => _isPlaying = false);
} else {
c.play();
setState(() => _isPlaying = true);
}
_startHideTimer(); _startHideTimer();
} catch (_) {
// ignore if controller disposed
}
} }
Future<void> _enterFullScreen() async { void _enterFullScreen() {
// prepare full screen orientation SystemChrome.setPreferredOrientations([
try {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight, DeviceOrientation.landscapeRight,
]); ]);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
await NativeOrientation.setLandscape();
} catch (_) {}
// push a new page with the same widget but isFullScreen = true Navigator.of(context)
await Navigator.of(context).push( .push(
MaterialPageRoute( MaterialPageRoute(
builder: (ctx) => Scaffold( builder: (ctx) => Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
@ -246,145 +145,55 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
), ),
), ),
), ),
); )
.then((_) {
// on return, restore orientation and UI and force rebuild SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
try { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
await NativeOrientation.setPortrait(); });
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
} catch (_) {}
if (!mounted) return;
// force a rebuild so parent layouts recalc (fixes overflow after exit)
setState(() {});
}
@override
void dispose() {
_hideTimer?.cancel();
_stopPositionTimer();
_sliderValue.dispose();
_removeControllerListenerSafely();
super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Compute constrained size so widget won't try to be infinite height in non-fullscreen usage. final screenW = MediaQuery.of(context).size.width;
final media = MediaQuery.of(context); final containerW = widget.isFullScreen ? double.infinity : screenW;
final screenW = media.size.width;
final screenH = media.size.height;
// Non-fullscreen preferred height: based on aspect ratio but not exceeding a fraction of screen height
final preferredNonFullHeight = min(screenW / (widget.aspectRatio <= 0 ? (16 / 9) : widget.aspectRatio),
screenH * 0.5); // at most 50% of screen height
// If isFullScreen, use available height minus safe areas; otherwise use preferredNonFullHeight
final containerW = widget.isFullScreen ? screenW : screenW;
final containerH = widget.isFullScreen final containerH = widget.isFullScreen
? screenH ? double.infinity
: preferredNonFullHeight; : containerW / widget.aspectRatio;
return Center( return Center(
child: SizedBox( child: SizedBox(
width: containerW, width: containerW,
height: containerH, height: containerH,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent, //
onTap: _toggleControls, onTap: _toggleControls,
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
// Video or cover: use AspectRatio to avoid forcing Column to expand //
_buildVideoOrCover(containerW, containerH), if (widget.controller != null &&
if (widget.isFullScreen) widget.controller!.value.isInitialized)
Positioned( FittedBox(
top: MediaQuery.of(context).padding.top + 8, // / fit: BoxFit.contain,
left: 8, alignment: Alignment.center,
child: SafeArea( child: SizedBox(
top: true, width: widget.controller!.value.size.width,
bottom: false, height: widget.controller!.value.size.height,
child: ClipOval( child: VideoPlayer(widget.controller!),
child: Material(
color: Colors.black38, //
child: InkWell(
onTap: () async {
// pop UI pop .then
try {
await NativeOrientation.setPortrait();
} catch (_) {}
try {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
} catch (_) {}
Navigator.of(context).maybePop();
},
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.arrow_back,
color: Colors.white,
size: 22,
), ),
), )
), else
), if (widget.coverUrl.length > 0)
), Image.network(
),
),
// Controls overlay
if (_visibleControls) _buildControls(),
],
),
),
),
);
}
Widget _buildVideoOrCover(double containerW, double containerH) {
final c = widget.controller;
// If controller exists and is initialized (safe check), show video in AspectRatio
if (c != null) {
try {
if (c.value.isInitialized) {
// Use video's natural aspect ratio if available, otherwise widget.aspectRatio
final vidAspect = (c.value.size.height > 0) ? (c.value.size.width / c.value.size.height) : widget.aspectRatio;
return Center(
child: AspectRatio(
aspectRatio: vidAspect > 0 ? vidAspect : widget.aspectRatio,
child: VideoPlayer(c),
),
);
}
} catch (_) {
// controller may be disposed; fall through to show cover
}
}
// Fallback: show cover image (fills the container)
if (widget.coverUrl.isNotEmpty) {
return Image.network(
widget.coverUrl, widget.coverUrl,
fit: BoxFit.cover,
width: containerW, width: containerW,
height: containerH, height: containerH,
fit: BoxFit.cover, ),
errorBuilder: (_, __, ___) => Container(color: Colors.black),
);
}
// final fallback: black box //
return Container(color: Colors.black); if (_visibleControls)
} Positioned(
Widget _buildControls() {
// Use local copies to avoid repeated reads that might throw if ctrl disposed
final totalMs = _totalDuration.inMilliseconds.toDouble();
final sliderMax = totalMs > 0 ? totalMs : 1.0;
final sliderValue = _sliderValue.value.clamp(0.0, sliderMax).toDouble();
return Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
@ -396,7 +205,7 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
end: Alignment.topCenter, end: Alignment.topCenter,
colors: [ colors: [
Colors.black.withOpacity(0.7), Colors.black.withOpacity(0.7),
Colors.transparent, Colors.transparent
], ],
), ),
), ),
@ -422,26 +231,37 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
thumbColor: Colors.white, thumbColor: Colors.white,
overlayColor: Colors.white24, overlayColor: Colors.white24,
trackHeight: 2, trackHeight: 2,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 8), thumbShape: RoundSliderThumbShape(
enabledThumbRadius: 8),
),
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.white, //
inactiveTrackColor: Colors.grey[400],//
thumbColor: Colors.white, //
overlayColor: Colors.white.withAlpha(0x33), //
disabledActiveTrackColor: Colors.white, //
disabledInactiveTrackColor: Colors.grey[400],
disabledThumbColor: Colors.white,
), ),
child: Slider( child: Slider(
value: sliderValue, value: value,
min: 0, min: 0,
max: sliderMax, max: _totalDuration.inMilliseconds.toDouble(),
// allowSeek onChanged
onChanged: (v) { onChanged: (v) {
if (widget.allowSeek && widget.controller != null) { if (widget.allowSeek && widget.controller != null) {
try {
widget.controller!.seekTo(Duration(milliseconds: v.toInt())); widget.controller!.seekTo(Duration(milliseconds: v.toInt()));
setState(() => _currentPosition = Duration(milliseconds: v.toInt())); setState(() => _currentPosition = Duration(milliseconds: v.toInt()));
_sliderValue.value = v; _sliderValue.value = v;
_startHideTimer(); _startHideTimer();
} catch (_) {}
} }
}, },
), ),
), ),
), ),
), ),
),
SizedBox( SizedBox(
width: 110, width: 110,
child: Text( child: Text(
@ -456,22 +276,26 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
IconButton( IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: Icon( icon: Icon(
widget.isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, widget.isFullScreen
? Icons.fullscreen_exit
: Icons.fullscreen,
size: 28, size: 28,
color: Colors.white, color: Colors.white,
), ),
onPressed: () { onPressed: () {
if (widget.isFullScreen) { widget.isFullScreen
// if this widget is used inside a full screen route, popping will exit fullscreen ? Navigator.of(context).pop()
Navigator.of(context).maybePop(); : _enterFullScreen();
} else {
_enterFullScreen();
}
}, },
), ),
], ],
), ),
), ),
),
],
),
),
),
); );
} }

View File

@ -20,11 +20,10 @@ class ApiService {
// static const String publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3pM3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OKU6Hzm1ninpWeE+awIDAQAB' // static const String publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3pM3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OKU6Hzm1ninpWeE+awIDAQAB'
/// ///
static const String baseFacePath = static const String baseFacePath =
"https://qaaqwh.qhdsafety.com/whb_stu_face"; "https://qaaqwh.qhdsafety.com/whb_stu_face/";
/// ///
static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb"; static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb";
// static const String basePath = "http://192.168.0.37:8099/api";
/// ///
static const String baseImgPath = "https://file.zcloudchina.com/YTHFile"; static const String baseImgPath = "https://file.zcloudchina.com/YTHFile";
@ -37,6 +36,8 @@ class ApiService {
static const String projectManagerUrl = static const String projectManagerUrl =
'https://pm.qhdsafety.com/zy-projectManage'; 'https://pm.qhdsafety.com/zy-projectManage';
/// NFC
static const String baseNFCPath = "http://192.168.0.37:8099/api/app";
// /// // ///
// static const String baseFacePath = // static const String baseFacePath =
@ -369,19 +370,6 @@ U6Hzm1ninpWeE+awIDAQAB
}, },
); );
} }
static Future<Map<String, dynamic>> getVideoPermissions() {
return HttpManager().request(
basePath,
'/app/coursestudyvideorecord/getVideoPermissions',
method: Method.post,
withToken: true,
data: {
'USERNAME': SessionService.instance.username,
'CORPINFO_ID': SessionService.instance.corpinfoId,
'USER_ID': SessionService.instance.loginUserId,
},
);
}
/// ///
static Future<Map<String, dynamic>> getStudyDetailList( static Future<Map<String, dynamic>> getStudyDetailList(
@ -404,7 +392,6 @@ U6Hzm1ninpWeE+awIDAQAB
basePath, basePath,
'/app/edu/stagestudentrelation/getMyTask', '/app/edu/stagestudentrelation/getMyTask',
method: Method.post, method: Method.post,
withToken: true,
data: { data: {
'CLASSCURRICULUM_ID': CLASSCURRICULUM_ID, 'CLASSCURRICULUM_ID': CLASSCURRICULUM_ID,
'CLASS_ID': CLASS_ID, 'CLASS_ID': CLASS_ID,
@ -423,7 +410,6 @@ U6Hzm1ninpWeE+awIDAQAB
basePath, basePath,
'/app/edu/audioOrVideo/getVideoPlayInfoApp', '/app/edu/audioOrVideo/getVideoPlayInfoApp',
method: Method.post, method: Method.post,
withToken: true,
data: { data: {
'VIDEOCOURSEWARE_ID': VIDEOCOURSEWARE_ID, 'VIDEOCOURSEWARE_ID': VIDEOCOURSEWARE_ID,
'CORPINFO_ID': SessionService.instance.corpinfoId, 'CORPINFO_ID': SessionService.instance.corpinfoId,
@ -451,7 +437,6 @@ U6Hzm1ninpWeE+awIDAQAB
baseFacePath, baseFacePath,
'/app/user/getUserFaceTime', '/app/user/getUserFaceTime',
method: Method.post, method: Method.post,
withToken: true,
data: { data: {
'loading': false, 'loading': false,
'FACE_TIME': FACE_TIME, 'FACE_TIME': FACE_TIME,
@ -485,7 +470,6 @@ U6Hzm1ninpWeE+awIDAQAB
basePath, basePath,
'/app/edu/coursestudyvideorecord/getVideoProgress', '/app/edu/coursestudyvideorecord/getVideoProgress',
method: Method.post, method: Method.post,
withToken: true,
data: { data: {
'VIDEOCOURSEWARE_ID': VIDEOCOURSEWARE_ID, 'VIDEOCOURSEWARE_ID': VIDEOCOURSEWARE_ID,
'CURRICULUM_ID': CURRICULUM_ID, 'CURRICULUM_ID': CURRICULUM_ID,
@ -498,19 +482,34 @@ U6Hzm1ninpWeE+awIDAQAB
} }
/// ///
static Future<Map<String, dynamic>> fnSubmitPlayTime(Map data) { static Future<Map<String, dynamic>> fnSubmitPlayTime(
String VIDEOCOURSEWARE_ID,
String CURRICULUM_ID,
String IS_END,
int RESOURCETIME,
String CHAPTER_ID,
String STUDENT_ID,
String CLASSCURRICULUM_ID,
String CLASS_ID,
) {
return HttpManager().request( return HttpManager().request(
basePath, basePath,
'/app/edu/coursestudyvideorecord/save', '/app/edu/coursestudyvideorecord/save',
method: Method.post, method: Method.post,
withToken: true,
data: { data: {
...data, 'VIDEOCOURSEWARE_ID': VIDEOCOURSEWARE_ID,
'CURRICULUM_ID': CURRICULUM_ID,
'CHAPTER_ID': CHAPTER_ID,
'RESOURCETIME': RESOURCETIME,
'IS_END': IS_END,
'CLASS_ID': CLASS_ID,
'CLASSCURRICULUM_ID': CLASSCURRICULUM_ID,
'STUDENT_ID': STUDENT_ID,
'loading': false,
'USER_NAME': SessionService.instance.username, 'USER_NAME': SessionService.instance.username,
'CORPINFO_ID': SessionService.instance.corpinfoId, 'CORPINFO_ID': SessionService.instance.corpinfoId,
'USER_ID': SessionService.instance.loginUserId, 'USER_ID': SessionService.instance.loginUserId,
}, },
); );
} }
@ -3711,8 +3710,8 @@ U6Hzm1ninpWeE+awIDAQAB
/// ///
static Future<Map<String, dynamic>> getNfcPipeLineAreaList() { static Future<Map<String, dynamic>> getNfcPipeLineAreaList() {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/getPipelineAreaListAll', '/pipelineInspection/getPipelineAreaListAll',
method: Method.post, method: Method.post,
data: {"CORPINFO_ID": SessionService.instance.corpinfoId, 'STATUS': '0'}, data: {"CORPINFO_ID": SessionService.instance.corpinfoId, 'STATUS': '0'},
); );
@ -3723,8 +3722,8 @@ U6Hzm1ninpWeE+awIDAQAB
String PIPELINE_AREA_ID, String PIPELINE_AREA_ID,
) { ) {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/getEquipmentPipelineListAll', '/pipelineInspection/getEquipmentPipelineListAll',
method: Method.post, method: Method.post,
data: { data: {
"CORPINFO_ID": SessionService.instance.corpinfoId, "CORPINFO_ID": SessionService.instance.corpinfoId,
@ -3737,8 +3736,8 @@ U6Hzm1ninpWeE+awIDAQAB
///NFC ///NFC
static Future<Map<String, dynamic>> nfcTagAdd(Map data) { static Future<Map<String, dynamic>> nfcTagAdd(Map data) {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/nfcTagAdd', '/pipelineInspection/nfcTagAdd',
method: Method.post, method: Method.post,
data: { data: {
...data, ...data,
@ -3754,8 +3753,8 @@ U6Hzm1ninpWeE+awIDAQAB
int currentPage, int currentPage,
) { ) {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/getPatrolTaskList?showCount=$showCount&currentPage=$currentPage', '/pipelineInspection/getPatrolTaskList?showCount=$showCount&currentPage=$currentPage',
method: Method.post, method: Method.post,
data: { data: {
@ -3773,8 +3772,8 @@ U6Hzm1ninpWeE+awIDAQAB
Map data, Map data,
) { ) {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/getPatrolTaskDetailList?showCount=$showCount&currentPage=$currentPage', '/pipelineInspection/getPatrolTaskDetailList?showCount=$showCount&currentPage=$currentPage',
method: Method.post, method: Method.post,
data: { data: {
...data, ...data,
@ -3786,8 +3785,8 @@ U6Hzm1ninpWeE+awIDAQAB
static Future<Map<String, dynamic>> nfcWriteCheck(String NFC_CODE) { static Future<Map<String, dynamic>> nfcWriteCheck(String NFC_CODE) {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/nfcTagCheck', '/pipelineInspection/nfcTagCheck',
method: Method.post, method: Method.post,
data: { data: {
"CORPINFO_ID": SessionService.instance.corpinfoId, "CORPINFO_ID": SessionService.instance.corpinfoId,
@ -3801,8 +3800,8 @@ U6Hzm1ninpWeE+awIDAQAB
Map data, Map data,
) async { ) async {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/goEditNfcExceptionRecord', '/pipelineInspection/goEditNfcExceptionRecord',
method: Method.post, method: Method.post,
data: { data: {
...data, ...data,
@ -3816,8 +3815,8 @@ U6Hzm1ninpWeE+awIDAQAB
Map data, Map data,
) async { ) async {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/nfcExceptionRecordAdd', '/pipelineInspection/nfcExceptionRecordAdd',
method: Method.post, method: Method.post,
data: { data: {
...data, ...data,
@ -3831,8 +3830,8 @@ U6Hzm1ninpWeE+awIDAQAB
Map data, Map data,
) async { ) async {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/patrolRecordDetailSaveOrUpdate', '/pipelineInspection/patrolRecordDetailSaveOrUpdate',
method: Method.post, method: Method.post,
data: { data: {
...data, ...data,
@ -3846,8 +3845,8 @@ U6Hzm1ninpWeE+awIDAQAB
Map data, Map data,
) async { ) async {
return HttpManager().request( return HttpManager().request(
basePath, baseNFCPath,
'/app/pipelineInspection/goEditPatrolRecordDetailHidden', '/pipelineInspection/goEditPatrolRecordDetailHidden',
method: Method.post, method: Method.post,
data: { data: {
...data, ...data,
@ -3856,54 +3855,7 @@ U6Hzm1ninpWeE+awIDAQAB
}, },
); );
} }
// NFC // goEditPatrolTaskDetail
static Future<Map<String, dynamic>> addNFCImgFiles(
String imagePath,
Map data,
) async {
final file = File(imagePath);
if (!await file.exists()) {
throw ApiException('file_not_found', '图片不存在:$imagePath');
}
final fileName = file.path.split(Platform.pathSeparator).last;
return HttpManager().uploadFaceImage(
baseUrl: basePath,
path: '/app//imgfiles/add',
fromData: {
...data,
"CORPINFO_ID": SessionService.instance.corpinfoId,
"USER_ID": SessionService.instance.loginUserId,
'FFILE': await MultipartFile.fromFile(file.path, filename: fileName),
},
);
}
///
static Future<Map<String, dynamic>> deleteNFCImage(Map data) {
return HttpManager().request(
basePath,
'/app/app/imgfiles/delete',
method: Method.post,
data: {
...data,
"CORPINFO_ID": SessionService.instance.corpinfoId,
"USER_ID": SessionService.instance.loginUserId,
},
);
}
/// nfc
static Future<Map<String, dynamic>> nfcInspectionRecord(
int showCount,
int currentPage,
) async {
return HttpManager().request(
basePath,
'/app/pipelineInspection/getPatrolRecordList?showCount=$showCount&currentPage=$currentPage',
method: Method.post,
data: {
"CORPINFO_ID": SessionService.instance.corpinfoId,
"USER_ID": SessionService.instance.loginUserId,
},
);
}
} }

View File

@ -2,7 +2,6 @@ import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/tools/tools.dart';
/// ///
class ApiException implements Exception { class ApiException implements Exception {
@ -43,24 +42,24 @@ class HttpManager {
..add(InterceptorsWrapper(onError: (err, handler) { ..add(InterceptorsWrapper(onError: (err, handler) {
// TODO // TODO
// 401 // 401
if (err.response?.statusCode == 401) { // if (err.response?.statusCode == 401) {
// // //
onUnauthorized?.call(); // onUnauthorized?.call();
// // //
final apiException = ApiException( // final apiException = ApiException(
'提示', // '提示',
'您的账号已在其他设备登录,已自动下线' // '您的账号已在其他设备登录,已自动下线'
); // );
// // //
return handler.reject( // return handler.reject(
DioException( // DioException(
requestOptions: err.requestOptions, // requestOptions: err.requestOptions,
error: apiException, // error: apiException,
response: err.response, // response: err.response,
type: DioExceptionType.badResponse, // type: DioExceptionType.badResponse,
), // ),
); // );
} // }
handler.next(err); handler.next(err);
})); }));
} }
@ -73,65 +72,72 @@ class HttpManager {
Map<String, dynamic>? data, Map<String, dynamic>? data,
Map<String, dynamic>? params, Map<String, dynamic>? params,
CancelToken? cancelToken, CancelToken? cancelToken,
bool withToken = false, // false
}) async { }) async {
Response resp; Response resp;
final url = baseUrl + path; final url = baseUrl + path;
// headers
final headers = {
'Content-Type': Headers.formUrlEncodedContentType,
};
if (withToken) {
final token = SessionService.instance.studyToken;
if (token != null && token.isNotEmpty) {
headers['Token'] = token;
}
}
final options = Options( final options = Options(
method: method.name.toUpperCase(), method: method.name.toUpperCase(),
contentType: Headers.formUrlEncodedContentType, contentType: Headers.formUrlEncodedContentType,
headers: headers,
); );
try { try {
switch (method) { switch (method) {
case Method.get: case Method.get:
resp = await _dio.get(url, final queryParameters = <String, dynamic>{};
queryParameters: {...?params, ...?data}, if (params != null) queryParameters.addAll(params);
if (data != null) queryParameters.addAll(data);
resp = await _dio.get(
url,
queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options); options: options,
break; );
case Method.post:
resp = await _dio.post(url,
data: data,
queryParameters: params,
cancelToken: cancelToken,
options: options);
break; break;
case Method.put: case Method.put:
resp = await _dio.put(url, resp = await _dio.put(
url,
data: data, data: data,
queryParameters: params, queryParameters: params,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options); options: options,
);
break; break;
case Method.delete: case Method.delete:
resp = await _dio.delete(url, resp = await _dio.delete(
url,
queryParameters: params, queryParameters: params,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options); options: options,
);
break; break;
case Method.post:
resp = await _dio.post(
url,
data: data,
queryParameters: params,
cancelToken: cancelToken,
options: options,
);
} }
} on DioException catch (e) { } on DioException catch (e) {
if (e.error is ApiException) throw e.error as ApiException; // ApiException401
if (e.error is ApiException) {
throw e.error as ApiException;
}
//
throw ApiException('network_error', e.message ?? e.toString()); throw ApiException('network_error', e.message ?? e.toString());
} }
// JSON
final json = resp.data is Map<String, dynamic> final json = resp.data is Map<String, dynamic>
? resp.data as Map<String, dynamic> ? resp.data as Map<String, dynamic>
: <String, dynamic>{}; : <String, dynamic>{};
// final result = json['result'] as String?;
// final msg = json['msg'] as String? ?? json['message'] as String? ?? '';
// if (result != 'success') {
// // success
// throw ApiException(result ?? 'unknown', msg);
// }
return json; return json;
} }
} }

View File

@ -14,8 +14,6 @@ import 'package:flutter/services.dart'; // for TextInput.hide
// //
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
//
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
// //
class GlobalMessage { class GlobalMessage {
@ -93,7 +91,7 @@ void main() async {
); );
Future.delayed(const Duration(milliseconds: 100), () { Future.delayed(const Duration(milliseconds: 100), () {
GlobalMessage.showError('您的账号已在其他设备登录,已自动下线,请使用单一设备进行学习。'); GlobalMessage.showError('会话已过期,请重新登录');
}); });
}; };
// //
@ -125,7 +123,7 @@ class MyApp extends StatelessWidget {
title: '', title: '',
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
// push/pop TextField // push/pop TextField
navigatorObservers: [KeyboardUnfocusNavigatorObserver(),routeObserver], navigatorObservers: [KeyboardUnfocusNavigatorObserver()],
builder: (context, child) { builder: (context, child) {
return EasyLoading.init( return EasyLoading.init(
builder: (context, widget) { builder: (context, widget) {
@ -141,9 +139,6 @@ class MyApp extends StatelessWidget {
)(context, child); )(context, child);
}, },
theme: ThemeData( theme: ThemeData(
textTheme: const TextTheme(
bodyMedium: TextStyle(fontSize: 14), //
),
dividerTheme: const DividerThemeData( dividerTheme: const DividerThemeData(
color: Colors.black12, color: Colors.black12,
thickness: .5, thickness: .5,

View File

@ -393,7 +393,6 @@ class _CheckRecordDetailPageState extends State<CheckRecordDetailPage> {
// loadRequest // loadRequest
try { try {
await _controller!.loadRequest(Uri.parse('http://47.92.102.56:7811/file/fluteightmap/index.html')); await _controller!.loadRequest(Uri.parse('http://47.92.102.56:7811/file/fluteightmap/index.html'));
} catch (e, st) { } catch (e, st) {
debugPrint('loadRequest 错误: $e\n$st'); debugPrint('loadRequest 错误: $e\n$st');
} }

View File

@ -621,8 +621,6 @@ class _HiddenDangerAcceptancePageState extends State<HiddenDangerAcceptancePage>
case '3': return '标准排查清单检查'; case '3': return '标准排查清单检查';
case '4': return '专项检查'; case '4': return '专项检查';
case '5': return '安全检查'; case '5': return '安全检查';
case '6': return 'NFC设备巡检';
default: return ''; default: return '';
} }
} }

View File

@ -443,8 +443,6 @@ class _HiddenRecordDetailPageState extends State<HiddenRecordDetailPage> {
case '3': return '标准排查清单检查'; case '3': return '标准排查清单检查';
case '4': return '专项检查'; case '4': return '专项检查';
case '5': return '安全检查'; case '5': return '安全检查';
case '6': return 'NFC设备巡检';
default: return ''; default: return '';
} }
} }

View File

@ -94,14 +94,10 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
hs = data['hs'] ?? {}; hs = data['hs'] ?? {};
// //
videoList = data['hiddenVideo'] ?? [];
for (var img in data['hImgs']) { for (var img in data['hImgs']) {
if (img["FILEPATH"].toString().endsWith('.mp4')) {
videoList.add(img);
}else{
files.add(img["FILEPATH"]); files.add(img["FILEPATH"]);
} }
} videoList = data['hiddenVideo'] ?? [];
// List<dynamic> filesZheng = data['rImgs'] ?? []; // List<dynamic> filesZheng = data['rImgs'] ?? [];
for (var img in data['rImgs']) { for (var img in data['rImgs']) {
@ -740,8 +736,6 @@ class _PendingRectificationDetailPageState extends State<PendingRectificationDet
case '3': return '标准排查清单检查'; case '3': return '标准排查清单检查';
case '4': return '专项检查'; case '4': return '专项检查';
case '5': return '安全检查'; case '5': return '安全检查';
case '6': return 'NFC设备巡检';
default: return ''; default: return '';
} }
} }

View File

@ -199,8 +199,9 @@ class _HomeNfcAddPageState extends State<HomeNfcAddPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: MyAppbar(title: 'NFC标签写入' appBar: MyAppbar(title: 'NFC标签写入', actions: [
), TextButton(onPressed: _getNfcData, child: Text('读取',style: TextStyle(color: Colors.white),))
],),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: EdgeInsets.all(12), padding: EdgeInsets.all(12),

View File

@ -42,15 +42,13 @@ class HomeNfcCheckDangerPage extends StatefulWidget {
super.key, super.key,
required this.info, required this.info,
required this.facebookImages, required this.facebookImages,
required this.isNfcError, required this.isNfcError
required this.isEdit
}); });
final Map info; final Map info;
final List<String> facebookImages; final List<String> facebookImages;
// nfc // nfc
final bool isNfcError; final bool isNfcError;
final bool isEdit;
@override @override
State<HomeNfcCheckDangerPage> createState() => _HomeNfcCheckDangerPageState(); State<HomeNfcCheckDangerPage> createState() => _HomeNfcCheckDangerPageState();
@ -59,7 +57,6 @@ class HomeNfcCheckDangerPage extends StatefulWidget {
class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> { class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
late Map<String, dynamic> pd = {}; late Map<String, dynamic> pd = {};
OptionData? selectType; // null OptionData? selectType; // null
late bool unchecked = true;
final List<OptionData> _options = const [ final List<OptionData> _options = const [
OptionData( OptionData(
@ -85,7 +82,7 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
void initState() { void initState() {
// TODO: implement initState // TODO: implement initState
super.initState(); super.initState();
unchecked = widget.info['INSPECTED_FLAG'] == '0'; final bool unchecked = widget.info['INSPECTED_FLAG'] == '0';
if (!unchecked) { // if (!unchecked) { //
for (OptionData data in _options) { for (OptionData data in _options) {
if (data.label == widget.info['INSPECTION_RESULT']) { if (data.label == widget.info['INSPECTION_RESULT']) {
@ -104,36 +101,13 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
if (result['result'] == 'success') { if (result['result'] == 'success') {
setState(() { setState(() {
pd = result['pd']; pd = result['pd'];
List hImgs = result['hImgs'];
List gzImageList = result['rImgs'];
List<nfcImgData> imgs = [];
List<nfcImgData> videos = [];
if (hImgs.isNotEmpty) {
for (Map item in hImgs) {
String path = item['FILEPATH'];
if (path.contains('.mp4')) {
videos.add(nfcImgData(path: '${ApiService.baseImgPath}$path', id: item['IMGFILES_ID']));
}else{
imgs.add(nfcImgData(path: '${ApiService.baseImgPath}$path', id: item['IMGFILES_ID']));
}
}
pd['imgList'] = imgs;
pd['videoList'] = videos;
}
if (gzImageList.isNotEmpty) {
List<nfcImgData> zgImgs = [];
for (Map item in gzImageList) {
String path = item['FILEPATH'];
zgImgs.add(nfcImgData(path: '${ApiService.baseImgPath}$path', id: item['IMGFILES_ID']));
}
pd['gzImageList'] = zgImgs;
}
}); });
} }
} }
Future<void> _submit() async { Future<void> _submit() async {
// //
if (selectType == null) { if (selectType == null) {
// //
ToastUtil.showNormal(context, '请先选择检查结果'); ToastUtil.showNormal(context, '请先选择检查结果');
@ -141,48 +115,33 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
} }
Map data = { Map data = {
...widget.info, ...widget.info,
...pd,
'INSPECTION_RESULT': selectType?.label ?? '', 'INSPECTION_RESULT': selectType?.label ?? '',
'TYPE': '0', // (0- 1-) 'TYPE': '0', // (0- 1-)
}; };
LoadingDialogHelper.show(); LoadingDialogHelper.show();
// ,
data['CHECK_CONTENT'] = widget.info['INSPECTION_CONTENT'];
final result = await ApiService.nfcChekSubmit(data);
if (result['result'] == 'success') {
Map p = result['pd'];
String HIDDEN_ID = p['HIDDEN_ID'] ?? '';
// ,
if (selectType?.label == '不合格') { if (selectType?.label == '不合格') {
List<nfcImgData> imgList = pd['imgList'] ?? []; List imgList = pd['imgList'] ?? [];
List<nfcImgData> _videos = pd['videoList'] ?? []; List _videos = pd['videoList'] ?? [];
List<nfcImgData> zgImgList = pd['gzImageList'] ?? []; List zgImgList = pd['gzImageList'] ?? [];
for (int i = 0; i < imgList.length; i++) { for (int i = 0; i < imgList.length; i++) {
nfcImgData data = imgList[i]; await _reloadFeedBack(imgList[i], '3');
if (data.id.isEmpty) {
await _reloadFeedBack(imgList[i].path, '3', HIDDEN_ID);
}
} }
for (int i = 0; i < _videos.length; i++) { for (int i = 0; i < _videos.length; i++) {
nfcImgData data = _videos[i]; await _reloadFeedBack(_videos[i], '3');
if (data.id.isEmpty) {
await _reloadFeedBack(_videos[i].path, '3', HIDDEN_ID);
}
} }
for (int i = 0; i < zgImgList.length; i++) { for (int i = 0; i < zgImgList.length; i++) {
nfcImgData data = zgImgList[i]; await _reloadFeedBack(zgImgList[i], '4');
if (data.id.isEmpty) {
await _reloadFeedBack(zgImgList[i].path, '4', HIDDEN_ID);
} }
data = {...data,...pd};
} }
}
if (widget.isNfcError) { // nfc退
if (widget.facebookImages.isNotEmpty) { if (widget.facebookImages.isNotEmpty) {
// nfc // nfc
final List<String> uploaded = []; final List<String> uploaded = [];
for (int i = 0; i < widget.facebookImages.length; i++) { for (int i = 0; i < widget.facebookImages.length; i++) {
String imagePath = await _reloadFeedBack(widget.facebookImages[i], '30', widget.info['EQUIPMENT_PIPELINE_ID']); String imagePath = await _reloadFeedBack(widget.facebookImages[i], '30');
if (imagePath.isNotEmpty) { if (imagePath.isNotEmpty) {
uploaded.add(imagePath); uploaded.add(imagePath);
} }
@ -191,11 +150,14 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
data['PHOTO_URL'] = uploaded.join(','); data['PHOTO_URL'] = uploaded.join(',');
} }
} }
data['CHECK_CONTENT'] = widget.info['INSPECTION_CONTENT'];
final result = await ApiService.nfcChekSubmit(data);
LoadingDialogHelper.hide();
if (result['result'] == 'success') {
if (widget.isNfcError) { // nfc退
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
LoadingDialogHelper.hide();
ToastUtil.showSuccess(context, '提交成功');
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { } else {
@ -204,13 +166,13 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
} }
} }
Future<String> _reloadFeedBack(String imagePath, String type, String FOREIGN_KEY) async { Future<String> _reloadFeedBack(String imagePath, String type) async {
try { try {
Map data = { Map data = {
'TYPE': type, 'TYPE': type,
'FOREIGN_KEY': FOREIGN_KEY, 'FOREIGN_KEY': widget.info['EQUIPMENT_PIPELINE_ID'],
}; };
final raw = await ApiService.addNFCImgFiles(imagePath, data); final raw = await ApiService.addNormalImgFiles(imagePath, data);
if (raw['result'] == 'success') { if (raw['result'] == 'success') {
Map pd = raw['pd']; Map pd = raw['pd'];
return pd['FILEPATH'] ?? ""; return pd['FILEPATH'] ?? "";
@ -227,7 +189,7 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
void _pushDangerDetail() async { void _pushDangerDetail() async {
// pushPage pd pd // pushPage pd pd
final result = await pushPage<Map<String, dynamic>>( final result = await pushPage<Map<String, dynamic>>(
NfcCheckDangerDetail(info: pd, unchecked: unchecked, isEdit: widget.isEdit), NfcCheckDangerDetail(info: pd),
context, context,
); );
if (result != null && result.isNotEmpty) { if (result != null && result.isNotEmpty) {
@ -332,18 +294,12 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
if (widget.isEdit) {
if (value != "option2") { if (value != "option2") {
selectType = option; selectType = option;
} else { } else {
// //
_pushDangerDetail(); _pushDangerDetail();
} }
}else { //
if (value == "option2") {
_pushDangerDetail();
}
}
}); });
}, },
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@ -354,31 +310,24 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
IntrinsicWidth( SizedBox(
height: 30,
width: 90,
child: Row( child: Row(
children: [ children: [
Icon(icon, color: isSelected ? color : Colors.grey, size: 25), Icon(icon, color: isSelected ? color : Colors.grey, size: 30),
const SizedBox(width: 2), const SizedBox(width: 8),
Flexible( Flexible(
child: Row( child: Text(
children: [
Text(
label, label,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 14,
fontWeight: fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal, isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? color : Colors.grey[600], color: isSelected ? color : Colors.grey[600],
), ),
), ),
if (label == '不合格' && !unchecked)
Column(
children: [Icon(Icons.edit_note, color: Colors.red, size: 20,), const SizedBox(height: 10,)],
)
],
)
), ),
], ],
), ),
@ -439,6 +388,11 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
screenWidth: screenWidth, screenWidth: screenWidth,
item: item, item: item,
onImageTap: () { onImageTap: () {
if (item["REFERENCE_BASIS"] == "option1") {
// _getAlreadyUpImages(item);
} else if (item["REFERENCE_BASIS"] == "option2") {
// _goUnqualifiedPage(item);
}
}, },
); );
}).toList(), }).toList(),
@ -461,7 +415,6 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
children: [ children: [
_pendingTopCard(widget.info), _pendingTopCard(widget.info),
const Spacer(), const Spacer(),
if (widget.isEdit)
CustomButton( CustomButton(
enabled: canSubmit, enabled: canSubmit,
text: '提交', text: '提交',

View File

@ -14,14 +14,9 @@ import 'package:qhd_prevention/services/nfc_service.dart';
import 'package:qhd_prevention/tools/tools.dart'; import 'package:qhd_prevention/tools/tools.dart';
class HomeNfcDetailPage extends StatefulWidget { class HomeNfcDetailPage extends StatefulWidget {
const HomeNfcDetailPage({ const HomeNfcDetailPage({super.key, required this.info});
super.key,
required this.info,
required this.isEdit,
});
final Map info; final Map info;
final bool isEdit;
@override @override
State<HomeNfcDetailPage> createState() => _HomeNfcDetailPageState(); State<HomeNfcDetailPage> createState() => _HomeNfcDetailPageState();
@ -144,6 +139,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
_getNFCForUid(uid, parsedText); _getNFCForUid(uid, parsedText);
}, },
onError: (err) { onError: (err) {
ToastUtil.showError(context, '$err'); ToastUtil.showError(context, '$err');
LoadingDialogHelper.hide(); LoadingDialogHelper.hide();
}, },
@ -166,16 +162,17 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
try{ try{
Map parsedData = jsonDecode(parsedText); Map parsedData = jsonDecode(parsedText);
if (parsedData['PIPELINE_AREA_ID'] == item['PIPELINE_AREA_ID'] && if (parsedData['PIPELINE_AREA_ID'] == item['PIPELINE_AREA_ID'] &&
parsedData['EQUIPMENT_PIPELINE_ID'] == parsedData['EQUIPMENT_PIPELINE_ID'] == item['EQUIPMENT_PIPELINE_ID']) {
item['EQUIPMENT_PIPELINE_ID']) {
result = item; result = item;
} }
LoadingDialogHelper.hide(); LoadingDialogHelper.hide();
}catch(e){ }catch(e){
LoadingDialogHelper.hide(); LoadingDialogHelper.hide();
ToastUtil.showError(context, 'NFC设备数据错误'); ToastUtil.showError(context, 'NFC设备数据错误');
} }
} }
} }
if (result.isEmpty) { if (result.isEmpty) {
@ -186,14 +183,10 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
} }
LoadingDialogHelper.hide(); LoadingDialogHelper.hide();
Map data = { Map data = {...result, ...widget.info, "NFC_CODE": uid, 'MANUAL_CONFIRMATION': '0',
...result,
...widget.info,
"NFC_CODE": uid,
'MANUAL_CONFIRMATION': '0',
}; };
await pushPage( await pushPage(
HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false, isEdit: widget.isEdit,), HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false,),
context, context,
); );
_getTaskDetail(refresh: true); _getTaskDetail(refresh: true);
@ -303,7 +296,6 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
text: item['USER_NAME'] ?? '', text: item['USER_NAME'] ?? '',
), ),
ItemListWidget.singleLineTitleText( ItemListWidget.singleLineTitleText(
// maxLines: 1,
label: '涉及管道区域:', label: '涉及管道区域:',
isEditable: false, isEditable: false,
text: item['PIPELINE_AREAS_NAMES'] ?? '', text: item['PIPELINE_AREAS_NAMES'] ?? '',
@ -327,7 +319,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
final bool unchecked = item['INSPECTED_FLAG'] == '0'; final bool unchecked = item['INSPECTED_FLAG'] == '0';
return Container( return Container(
height: widget.isEdit ? 100 : 80, height: 100,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row( child: Row(
children: [ children: [
@ -364,25 +356,14 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
// //
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: (){
if (!unchecked) { if (!unchecked) {
Map data = { Map data = {...item, ...widget.info, "NFC_CODE": item['PIPELINE_AREA_ID'], 'MANUAL_CONFIRMATION': '0',
...item,
...widget.info,
"NFC_CODE": item['PIPELINE_AREA_ID'],
'MANUAL_CONFIRMATION': '0',
}; };
await pushPage( pushPage(
HomeNfcCheckDangerPage( HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false,),
info: data,
facebookImages: [],
isNfcError: false,
isEdit: widget.isEdit,
),
context, context,
); );
_getTaskDetail(refresh: true);
} }
}, },
child: Column( child: Column(
@ -401,7 +382,6 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
Text('NFC编码${item['NFC_CODE'] ?? ''}'), Text('NFC编码${item['NFC_CODE'] ?? ''}'),
const SizedBox(height: 6), const SizedBox(height: 6),
unchecked unchecked
? widget.isEdit
? Row( ? Row(
spacing: 10, spacing: 10,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -413,9 +393,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
height: 35, height: 35,
// width: 120, // width: 120,
alignment: Alignment.center, alignment: Alignment.center,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 1),
vertical: 1,
),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: const LinearGradient(
colors: [ colors: [
@ -455,7 +433,6 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
), ),
], ],
) )
: const SizedBox()
: Text( : Text(
'检查时间:${item['PATROL_TIME'] ?? ''}', '检查时间:${item['PATROL_TIME'] ?? ''}',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
@ -463,12 +440,13 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
), ),
], ],
), ),
), )
), ),
// //
const SizedBox(width: 8), const SizedBox(width: 8),
if (!unchecked) const Icon(Icons.chevron_right, color: Colors.grey), if (!unchecked)
const Icon(Icons.chevron_right, color: Colors.grey),
], ],
), ),
); );

View File

@ -22,7 +22,7 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
// - // -
int _pendingPage = 1; int _pendingPage = 1;
final int _pageSize = 20; final int _pageSize = 10;
bool _pendingLoading = false; bool _pendingLoading = false;
bool _pendingHasMore = true; bool _pendingHasMore = true;
final ScrollController _pendingScrollController = ScrollController(); final ScrollController _pendingScrollController = ScrollController();
@ -143,6 +143,7 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
await _getTaskDetailList(page: _recordPage + 1, replace: false); await _getTaskDetailList(page: _recordPage + 1, replace: false);
} }
// ---------- ----------
//NFC //NFC
Future<void> _getTaskList({required int page, required bool replace}) async { Future<void> _getTaskList({required int page, required bool replace}) async {
setState(() { setState(() {
@ -206,39 +207,59 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
_recordLoading = true; _recordLoading = true;
}); });
try { try {
final res = await ApiService.nfcInspectionRecord(_pageSize, page); // final res = await ApiService.nfcTaskDetailList(_pageSize, page);
// // List<dynamic>? list;
// List<dynamic>? list; // if (res == null) {
if (res['result'] == 'success') { // list = [];
List<dynamic> list = res['varList']; // } else if (res is List) {
// list = res;
final parsed = // } else if (res is Map) {
list.map<Map<String, dynamic>>((e) { // list = (res['data'] ?? res['list'] ?? res['items'] ?? []) as List<dynamic>?;
return e; // }
}).toList(); // list ??= [];
// //
// < pageSize // final parsed = list.map<Map<String, dynamic>>((e) {
final bool gotLessThanPage = parsed.length < _pageSize; // if (e is Map) {
// return {
if (replace) { // 'title': (e['title'] ?? e['TASK_NAME'] ?? e['taskName'] ?? e['name'] ?? '未命名').toString(),
setState(() { // 'status': (e['status'] ?? e['TASK_STATUS'] ?? '已巡检').toString(),
_recordList.clear(); // 'department': (e['department'] ?? e['DEPARTMENT'] ?? e['dept'] ?? '').toString(),
_recordList.addAll(parsed); // 'owner': (e['owner'] ?? e['OWNER'] ?? e['person'] ?? '').toString(),
_recordPage = 1; // 'unType': (e['unType'] ?? e['UN_TYPE'] ?? e['un_type'] ?? '').toString(),
_recordHasMore = !gotLessThanPage; // 'cycle': (e['cycle'] ?? e['CYCLE'] ?? '').toString(),
}); // 'points': (e['points'] ?? e['POINTS'] ?? '').toString(),
} else { // };
setState(() { // } else {
_recordList.addAll(parsed); // final s = e?.toString() ?? '';
_recordPage = page; // return {
if (parsed.isEmpty) // 'title': s,
_recordHasMore = false; // 'status': '已巡检',
else if (parsed.length < _pageSize) // 'department': '',
_recordHasMore = false; // 'owner': '',
}); // 'unType': '',
} // 'cycle': '',
} // 'points': '',
// };
// }
// }).toList();
//
// final bool gotLessThanPage = parsed.length < _pageSize;
//
// if (replace) {
// setState(() {
// _recordList.clear();
// _recordList.addAll(parsed);
// _recordPage = 1;
// _recordHasMore = !gotLessThanPage;
// });
// } else {
// setState(() {
// _recordList.addAll(parsed);
// _recordPage = page;
// if (parsed.isEmpty) _recordHasMore = false;
// else if (parsed.length < _pageSize) _recordHasMore = false;
// });
// }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of( ScaffoldMessenger.of(
@ -342,7 +363,7 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
pushPage(HomeNfcDetailPage(info: item, isEdit: true,), context); pushPage(HomeNfcDetailPage(info: item), context);
}, },
child: _pendingCard(item, false), child: _pendingCard(item, false),
), ),
@ -382,14 +403,9 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
} }
} }
final item = _recordList[index]; final item = _recordList[index];
return GestureDetector( return Container(
onTap: () { margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
pushPage(HomeNfcDetailPage(info: item, isEdit: false,), context);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
child: _pendingCard(item, true), child: _pendingCard(item, true),
),
); );
}, },
), ),
@ -398,35 +414,12 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
/// ///
Widget _pendingCard(Map<String, dynamic> item, bool isFinish) { Widget _pendingCard(Map<String, dynamic> item, bool isFinish) {
String finishState = ''; return SizedBox(
Color textColor = Colors.blue; height: 180,
if (isFinish) { child: Stack(
if (item['PATROL_STATUS'] == '0') { clipBehavior: Clip.none,
finishState = '已巡检';
textColor = Colors.blue;
} else if (item['PATROL_STATUS'] == '1') {
finishState = '超期未巡检';
textColor = Colors.red;
} else if (item['PATROL_STATUS'] == '2') {
finishState = '巡检中';
textColor = Colors.deepOrange;
}
} else {
if (FormUtils.hasValue(item, 'INSPECTED_POINTS')) {
final inspected = (item['INSPECTED_POINTS'] is int) ? item['INSPECTED_POINTS'] as int : int.tryParse('${item['INSPECTED_POINTS']}') ?? 0;
finishState = inspected > 0 ? '巡检中' : '待巡检';
textColor = inspected > 0 ? Colors.deepOrange : Colors.blue;
}
}
return Container(
margin: const EdgeInsets.symmetric(vertical: 0, horizontal: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Stack + /
Stack(
children: [ children: [
// &
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
child: Image.asset( child: Image.asset(
@ -436,6 +429,8 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
// &
Positioned( Positioned(
top: 12, top: 12,
left: 12, left: 12,
@ -448,38 +443,57 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
), ),
), ),
), ),
if (!isFinish)
Positioned( Positioned(
top: 12, top: 12,
right: 12, right: 12,
child: Container( child: Container(
height: 30, height: 30,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.5), color: Colors.white.withOpacity(0.5),
borderRadius: BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
), ),
child: Center( child: Center(
child: Text( child: Text(
finishState, item['INSPECTED_POINTS'] > 0 ? '巡检中' : '待巡检',
style: TextStyle(color: textColor, fontSize: 14), style: TextStyle(
color:
item['INSPECTED_POINTS'] as int > 0
? Colors.blue
: Colors.deepOrange,
fontSize: 14,
), ),
), ),
), ),
), ),
],
), ),
// 使 Transform //
Transform.translate( Positioned(
offset: const Offset(0, -20), left: 0,
right: 0,
top: 50, //
child: Container( child: Container(
width: double.infinity, margin: const EdgeInsets.symmetric(horizontal: 0),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(10), borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
boxShadow: const [ boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 4, offset: Offset(0, 1)) BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 1),
),
], ],
), ),
child: _buildInfoGrid(item, isFinish), child: _buildInfoGrid(item, isFinish),
@ -490,13 +504,9 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
); );
} }
/// ///
Widget _buildInfoGrid(Map<String, dynamic> item, bool isFinish) { Widget _buildInfoGrid(Map<String, dynamic> item, bool isFinish) {
return Column( return Row(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
@ -506,7 +516,6 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
const SizedBox(height: 8), const SizedBox(height: 8),
Text('巡检类型:${item['PATROL_TYPE_NAME'] ?? ''}'), Text('巡检类型:${item['PATROL_TYPE_NAME'] ?? ''}'),
const SizedBox(height: 8), const SizedBox(height: 8),
isFinish ? Text('巡检次数:${item['ACTUAL_PATROL_TIMES'] ?? '0'}') :
Text('已巡点位:${item['INSPECTED_POINTS'] ?? '0'}'), Text('已巡点位:${item['INSPECTED_POINTS'] ?? '0'}'),
], ],
), ),
@ -514,33 +523,18 @@ class _HomeNfcListPageState extends State<HomeNfcListPage>
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text('负责人:${item['USER_NAME'] ?? ''}'), Text('负责人:${item['USER_NAME'] ?? ''}'),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('巡检周期:${item['PATROL_PERIOD_NAME'] ?? ''}'), Text('巡检周期:${item['PATROL_PERIOD_NAME'] ?? ''}'),
isFinish
? Text('涉及管道区域:${item['department'] ?? ''}')
: const SizedBox(height: 25),
], ],
), ),
), ),
], ],
),
if (isFinish)
const SizedBox(height: 10,),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: isFinish
? Text(
'涉及管道区域:${item['PIPELINE_AREAS_NAMES'] ?? ''}',
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
: const SizedBox(height: 0),)
],
)
],
); );
} }
} }

View File

@ -12,8 +12,6 @@ import 'package:qhd_prevention/customWidget/department_person_picker.dart';
import 'package:qhd_prevention/customWidget/department_picker.dart'; import 'package:qhd_prevention/customWidget/department_picker.dart';
import 'package:qhd_prevention/customWidget/department_picker_hidden_type.dart'; import 'package:qhd_prevention/customWidget/department_picker_hidden_type.dart';
import 'package:qhd_prevention/customWidget/department_picker_two.dart'; import 'package:qhd_prevention/customWidget/department_picker_two.dart';
import 'package:qhd_prevention/customWidget/full_screen_video_page.dart';
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart';
import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/pages/my_appbar.dart';
@ -21,23 +19,10 @@ import 'package:qhd_prevention/tools/tools.dart';
import '../../../customWidget/photo_picker_row.dart'; import '../../../customWidget/photo_picker_row.dart';
import '../../../http/ApiService.dart'; import '../../../http/ApiService.dart';
class nfcImgData {
final String path;
final String id;
const nfcImgData({
required this.path,
required this.id,
});
}
class NfcCheckDangerDetail extends StatefulWidget { class NfcCheckDangerDetail extends StatefulWidget {
const NfcCheckDangerDetail({super.key, required this.info, required this.unchecked, required this.isEdit}); const NfcCheckDangerDetail({super.key, required this.info});
final bool unchecked;
final Map<String, dynamic> info; final Map<String, dynamic> info;
final bool isEdit;
@override @override
State<NfcCheckDangerDetail> createState() => _NfcCheckDangerDetailState(); State<NfcCheckDangerDetail> createState() => _NfcCheckDangerDetailState();
@ -55,11 +40,11 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
Map<String, dynamic> _personCache = {}; Map<String, dynamic> _personCache = {};
// //
late List<nfcImgData> imgList = []; late List<String> imgList = [];
late List<nfcImgData> _videos = []; late List<String> _videos = [];
// //
late List<nfcImgData> zgImgList = []; late List<String> zgImgList = [];
@override @override
void initState() { void initState() {
@ -70,7 +55,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
imgList = pd['imgList'] ?? []; imgList = pd['imgList'] ?? [];
_videos = pd['videoList'] ?? []; _videos = pd['videoList'] ?? [];
zgImgList = pd['gzImageList'] ?? []; zgImgList = pd['gzImageList'] ?? [];
_isDanger = pd['RECTIFICATIONTYPE'] == '1';
} }
_getHazardLevel(); _getHazardLevel();
} }
@ -106,22 +91,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
print('Error fetching data: $e'); print('Error fetching data: $e');
} }
} }
String _getIDForPath(String path, List<nfcImgData> imgList) {
for (nfcImgData data in imgList) {
if (data.path == path) {
return data.id;
}
}
return '';
}
nfcImgData _getImageDataForPath(String path, List<nfcImgData> list) {
for (nfcImgData data in list) {
if (data.path == path) {
return data;
}
}
return nfcImgData(path: '', id: '');
}
Future<void> _pickHazardType() async { Future<void> _pickHazardType() async {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
@ -264,29 +234,14 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
RepairedPhotoSection( RepairedPhotoSection(
title: "隐患照片", title: "隐患照片",
maxCount: 4, maxCount: 4,
initialMediaPaths: imgList.map((item) => item.path).toList(), initialMediaPaths: imgList,
mediaType: MediaType.image, mediaType: MediaType.image,
isEdit: widget.isEdit,
isShowAI: true, isShowAI: true,
onMediaAdded: (localPath) { onMediaAdded: (localPath) {
imgList.add(nfcImgData(path: localPath, id: '')); imgList.add(localPath);
}, },
onMediaRemoved: (localPath) async { onMediaRemoved: (localPath) {
if (localPath.startsWith('http')) { imgList.remove(localPath);
final result = await _removeFileWithType('img', _getIDForPath(localPath, imgList));
if (result) {
setState(() {
imgList.remove(_getImageDataForPath(localPath, imgList));
});
}
}else{
setState(() {
imgList.remove(_getImageDataForPath(localPath, imgList));
});
}
},
onMediaTapped: (path) {
presentOpaque(SingleImageViewer(imageUrl: path), context);
}, },
onChanged: (v) {}, onChanged: (v) {},
onAiIdentify: () { onAiIdentify: () {
@ -299,41 +254,23 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
ToastUtil.showNormal(context, "识别暂时只能上传一张图片"); ToastUtil.showNormal(context, "识别暂时只能上传一张图片");
return; return;
} }
nfcImgData data = imgList.first; _identifyImg(imgList[0]);
_identifyImg(data.path);
}, },
), ),
RepairedPhotoSection( RepairedPhotoSection(
title: "隐患视频", title: "隐患视频",
maxCount: 1, maxCount: 1,
isEdit: widget.isEdit,
initialMediaPaths: _videos.map((item) => item.path).toList(),
mediaType: MediaType.video, mediaType: MediaType.video,
onChanged: (v) {}, onChanged: (v) {},
onMediaRemoved: (localPath) async { onMediaRemoved: (localPath) {
if (localPath.startsWith('http')) { _videos = [localPath];
final result = await _removeFileWithType('img', _getIDForPath(localPath, _videos));
if (result) {
setState(() {
_videos.remove(_getImageDataForPath(localPath, _videos));
});
}
}else{
setState(() {
_videos.remove(_getImageDataForPath(localPath, _videos));
});
}
}, },
onMediaTapped: (path) {
showDialog(
context: context,
barrierColor: Colors.black54,
builder: (_) => VideoPlayerPopup(videoUrl: path),
); },
onMediaAdded: (localPath) { onMediaAdded: (localPath) {
_videos.add(nfcImgData(path: localPath, id: '')); _videos = [];
},
onAiIdentify: () {
// AI
}, },
onAiIdentify: () {},
), ),
], ],
), ),
@ -341,7 +278,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
ItemListWidget.multiLineTitleTextField( ItemListWidget.multiLineTitleTextField(
label: '隐患描述', label: '隐患描述',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
text: pd['HIDDENDESCR'] ?? '', text: pd['HIDDENDESCR'] ?? '',
hintText: '请对隐患进行详细描述(必填项)', hintText: '请对隐患进行详细描述(必填项)',
@ -355,7 +292,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
ItemListWidget.multiLineTitleTextField( ItemListWidget.multiLineTitleTextField(
label: '隐患部位', label: '隐患部位',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
// controller: TextEditingController(text: pd['HIDDENPART'] ?? ''), // controller: TextEditingController(text: pd['HIDDENPART'] ?? ''),
text: pd['HIDDENPART'] ?? '', text: pd['HIDDENPART'] ?? '',
@ -371,14 +308,14 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '隐患级别', label: '隐患级别',
isRequired: false, isRequired: false,
isEditable: widget.isEdit, isEditable: true,
text: pd['HIDDENLEVELNAME'] ?? '', text: pd['HIDDENLEVELNAME'] ?? '',
onTap: chooseDangerLevel, onTap: chooseDangerLevel,
), ),
const Divider(), const Divider(),
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '隐患类型', label: '隐患类型',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
text: pd['HIDDENTYPE_NAME'] ?? '', text: pd['HIDDENTYPE_NAME'] ?? '',
onTap: _pickHazardType, onTap: _pickHazardType,
@ -389,8 +326,6 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
title: "是否立即整改", title: "是否立即整改",
horizontalPadding: 0, horizontalPadding: 0,
verticalPadding: 0, verticalPadding: 0,
text: _isDanger ? '立即整改' : '限期整改',
isEdit: widget.isEdit,
yesLabel: "", yesLabel: "",
noLabel: "", noLabel: "",
groupValue: _isDanger, groupValue: _isDanger,
@ -407,7 +342,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
children: [ children: [
ItemListWidget.multiLineTitleTextField( ItemListWidget.multiLineTitleTextField(
label: '整改描述', label: '整改描述',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
text: pd['RECTIFYDESCR'] ?? '', text: pd['RECTIFYDESCR'] ?? '',
hintText: '请对隐患进行详细描述(必填项)', hintText: '请对隐患进行详细描述(必填项)',
@ -423,29 +358,15 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
horizontalPadding: 12, horizontalPadding: 12,
title: "整改后图片", title: "整改后图片",
maxCount: 4, maxCount: 4,
isEdit: widget.isEdit, initialMediaPaths: zgImgList,
initialMediaPaths: zgImgList.map((item) => item.path).toList(),
mediaType: MediaType.image, mediaType: MediaType.image,
isShowAI: false, isShowAI: false,
onMediaTapped: (path) {
presentOpaque(SingleImageViewer(imageUrl: path), context);
},
onMediaAdded: (localPath) { onMediaAdded: (localPath) {
zgImgList.add(nfcImgData(path: localPath, id: '')); zgImgList.add(localPath);
}, },
onMediaRemoved: (localPath) async { onMediaRemoved: (localPath) {
if (localPath.startsWith('http')) { zgImgList.remove(localPath);
final result = await _removeFileWithType('img', _getIDForPath(localPath, zgImgList));
if (result) {
setState(() {
zgImgList.remove(_getImageDataForPath(localPath, zgImgList));
});
}
}else{
setState(() {
zgImgList.remove(_getImageDataForPath(localPath, zgImgList));
});
}
}, },
onChanged: (v) {}, onChanged: (v) {},
onAiIdentify: () {}, onAiIdentify: () {},
@ -457,7 +378,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
children: [ children: [
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '整改责任部门', label: '整改责任部门',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
text: pd['RECTIFICATIONDEPTNAME'] ?? '', text: pd['RECTIFICATIONDEPTNAME'] ?? '',
onTap: () { onTap: () {
@ -467,7 +388,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
const Divider(), const Divider(),
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '整改责任人', label: '整改责任人',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
text: pd['RECTIFICATIONORNAME'] ?? '', text: pd['RECTIFICATIONORNAME'] ?? '',
onTap: () { onTap: () {
@ -477,7 +398,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
const Divider(), const Divider(),
ItemListWidget.selectableLineTitleTextRightButton( ItemListWidget.selectableLineTitleTextRightButton(
label: '整改期限', label: '整改期限',
isEditable: widget.isEdit, isEditable: true,
isRequired: false, isRequired: false,
text: pd['RECTIFICATIONDEADLINE'] ?? '', text: pd['RECTIFICATIONDEADLINE'] ?? '',
onTap: () { onTap: () {
@ -506,13 +427,9 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
SizedBox(height: 30), SizedBox(height: 30),
CustomButton( CustomButton(
onPressed: () { onPressed: () {
if (widget.isEdit) {
_riskListCheckAppAdd(); _riskListCheckAppAdd();
}else{
Navigator.pop(context, pd);
}
}, },
text: widget.isEdit ? "确定" : "返回", text: "确定",
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
), ),
SizedBox(height: 30), SizedBox(height: 30),
@ -522,19 +439,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
), ),
); );
} }
Future<bool> _removeFileWithType(String type, String id) async{
if (id.isEmpty) {
return true;
}
Map data = {'IMGFILES_ID': id};
final result = await ApiService.deleteNFCImage(data);
if (result['result'] == 'success') {
return true;
}else{
return false;
}
// return
}
Future<void> _riskListCheckAppAdd() async { Future<void> _riskListCheckAppAdd() async {
if (imgList.isEmpty) { if (imgList.isEmpty) {
ToastUtil.showNormal(context, '请上传隐患图片'); ToastUtil.showNormal(context, '请上传隐患图片');
@ -558,7 +463,7 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
ToastUtil.showNormal(context, '请填整改描述'); ToastUtil.showNormal(context, '请填整改描述');
return; return;
} }
if (zgImgList.length == 0) { if (zgImgList.isEmpty) {
ToastUtil.showNormal(context, '请上传整改后图片'); ToastUtil.showNormal(context, '请上传整改后图片');
return; return;
} }
@ -576,16 +481,14 @@ class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
return; return;
} }
} }
List HIDDENTYPE = pd['HIDDENTYPE'] ?? [];
final HIDDENTYPE = pd['HIDDENTYPE']; pd['RECTIFICATIONTYPE'] = _isDanger ? '1' : '2';
if (HIDDENTYPE is List) { pd['CREATOR'] = SessionService.instance.loginUserId;
pd['SOURCE'] = '6';
pd['HIDDENTYPE1'] = HIDDENTYPE.length > 0 ? HIDDENTYPE[0] : ""; pd['HIDDENTYPE1'] = HIDDENTYPE.length > 0 ? HIDDENTYPE[0] : "";
pd['HIDDENTYPE2'] = HIDDENTYPE.length > 1 ? HIDDENTYPE[1] : ""; pd['HIDDENTYPE2'] = HIDDENTYPE.length > 1 ? HIDDENTYPE[1] : "";
pd['HIDDENTYPE3'] = HIDDENTYPE.length > 2 ? HIDDENTYPE[2] : ""; pd['HIDDENTYPE3'] = HIDDENTYPE.length > 2 ? HIDDENTYPE[2] : "";
}
pd['CREATOR'] = SessionService.instance.loginUserId;
pd['SOURCE'] = '6';
pd['RECTIFICATIONTYPE'] = _isDanger ? '1' : '2';
pd['imgList'] = imgList; pd['imgList'] = imgList;
pd['videoList'] = _videos; pd['videoList'] = _videos;
pd['gzImageList'] = zgImgList; pd['gzImageList'] = zgImgList;

View File

@ -185,7 +185,7 @@ class _NfcQuestionFecebookState extends State<NfcQuestionFecebook> {
'DESCRIPTION': text, 'DESCRIPTION': text,
}; };
pushPage(HomeNfcCheckDangerPage(info: data, facebookImages: _images, isNfcError: true,isEdit: true,), context); pushPage(HomeNfcCheckDangerPage(info: data, facebookImages: _images, isNfcError: true,), context);
// String imagePaths = ""; // String imagePaths = "";
// for (int i = 0; i < _images.length; i++) { // for (int i = 0; i < _images.length; i++) {

View File

@ -42,13 +42,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
LoadingDialogHelper.hide(); LoadingDialogHelper.hide();
} }
} }
Future<void> _nowStudy(Map item) async {
final result = await ApiService.getVideoPermissions();
if (result['result'] == 'success') {
SessionService.instance.setStudyToken(result['token'] ?? '');
pushPage(StudyDetailPage(item, widget.studyData['STUDENT_ID']), context);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -88,7 +82,7 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
ApiService.baseImgPath + item['COVERPATH'], ApiService.baseImgPath + item['COVERPATH'],
width: 100, width: 100,
height: 80, height: 80,
fit: BoxFit.fill, fit: BoxFit.cover,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
@ -109,21 +103,23 @@ class _StudyClassListPageState extends State<StudyClassListPage> {
overflow: TextOverflow.visible, overflow: TextOverflow.visible,
style: const TextStyle(color: Colors.black54), style: const TextStyle(color: Colors.black54),
),), ),),
], Column(
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const SizedBox(), SizedBox(),
CustomButton( CustomButton(
text: "立即学习", text: "立即学习",
backgroundColor: Colors.blue, backgroundColor: Colors.blue,
height: 38, height: 38,
onPressed: () { onPressed: () {
_nowStudy(item); pushPage(StudyDetailPage(item, widget.studyData['STUDENT_ID']), context);
}, },
), ),
],) ],
)
],
),
], ],
), ),
), ),

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/remote_file_page.dart';
import 'package:qhd_prevention/pages/home/study/take_exam_page.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/home/study/study_practise_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/pages/my_appbar.dart';
@ -15,12 +14,15 @@ import '../../../customWidget/video_player_widget.dart';
import '../../../http/HttpManager.dart'; import '../../../http/HttpManager.dart';
import 'face_ecognition_page.dart'; import 'face_ecognition_page.dart';
enum TakeExamType { video_study, strengththen, list } enum TakeExamType {
video_study,
strengththen,
list
}
class StudyDetailPage extends StatefulWidget { class StudyDetailPage extends StatefulWidget {
final Map studyDetailDetail; final Map studyDetailDetail;
final String studentId; final String studentId;
const StudyDetailPage(this.studyDetailDetail, this.studentId, {super.key}); const StudyDetailPage(this.studyDetailDetail, this.studentId, {super.key});
@override @override
@ -67,7 +69,6 @@ class _StudyDetailPageState extends State<StudyDetailPage>
_tabController.dispose(); _tabController.dispose();
_videoController?.removeListener(_onTimeUpdate); _videoController?.removeListener(_onTimeUpdate);
_videoController?.dispose(); _videoController?.dispose();
_videoController = null;
_faceTimer?.cancel(); _faceTimer?.cancel();
super.dispose(); super.dispose();
} }
@ -75,8 +76,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
Future<void> _showFaceIntro() async { Future<void> _showFaceIntro() async {
await showDialog( await showDialog(
context: context, context: context,
builder: builder: (_) => CustomAlertDialog(
(_) => CustomAlertDialog(
title: '温馨提示', title: '温馨提示',
content: content:
'重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。', '重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。',
@ -169,12 +169,8 @@ class _StudyDetailPageState extends State<StudyDetailPage>
if ((data['IS_VIDEO'] ?? 0) == 1) { if ((data['IS_VIDEO'] ?? 0) == 1) {
// document // document
if (data['VIDEOFILES'] != null) { if (data['VIDEOFILES'] != null) {
_videoController?.pause();
await pushPage( await pushPage(
RemoteFilePage( StudyPractisePage(videoCoursewareId: data['VIDEOCOURSEWARE_ID']),
fileUrl: ApiService.baseImgPath + data['VIDEOFILES'],
countdownSeconds: 10,
),
context, context,
); );
await _submitPlayTime( await _submitPlayTime(
@ -182,9 +178,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
seconds: int.parse(data['VIDEOTIME'] ?? '0'), seconds: int.parse(data['VIDEOTIME'] ?? '0'),
); );
} else { } else {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, const SnackBar(content: Text('课件文件资源已失效,请联系管理员')),
).showSnackBar(const SnackBar(content: Text('课件文件资源已失效,请联系管理员'))); );
} }
} else { } else {
// video // video
@ -203,9 +199,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
if (passed == true) { if (passed == true) {
await onPass(); await onPass();
} else { } else {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, const SnackBar(content: Text('人脸验证未通过,无法继续')),
).showSnackBar(const SnackBar(content: Text('人脸验证未通过,无法继续'))); );
} }
} else { } else {
await onPass(); await onPass();
@ -225,8 +221,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
); );
final raw = prog['pd']?['RESOURCETIME']; final raw = prog['pd']?['RESOURCETIME'];
final seen = final seen = (() {
(() {
if (raw == null) return 0; if (raw == null) return 0;
// //
if (raw is num) return raw.toInt(); if (raw is num) return raw.toInt();
@ -252,16 +247,15 @@ class _StudyDetailPageState extends State<StudyDetailPage>
..addListener(_onTimeUpdate); ..addListener(_onTimeUpdate);
} }
void _onTimeUpdate() { void _onTimeUpdate() {
if (_videoController == null || !_videoController!.value.isPlaying) return; if (_videoController == null || !_videoController!.value.isPlaying) return;
final curr = _videoController!.value.position; final curr = _videoController!.value.position;
if (!_throttleFlag && (curr - _lastReported).inSeconds >= 5) { if (!_throttleFlag && (curr - _lastReported).inSeconds >= 5) {
_throttleFlag = true; _throttleFlag = true;
_lastReported = curr; _lastReported = curr;
_submitPlayTime( _submitPlayTime(end: false, seconds: curr.inSeconds)
end: false, .whenComplete(() => _throttleFlag = false);
seconds: curr.inSeconds,
).whenComplete(() => _throttleFlag = false);
} }
final pos = _videoController!.value.position; final pos = _videoController!.value.position;
final dur = _videoController!.value.duration; final dur = _videoController!.value.duration;
@ -279,34 +273,29 @@ class _StudyDetailPageState extends State<StudyDetailPage>
if (_currentVideoData == null) return; if (_currentVideoData == null) return;
try { try {
Map data = { final resData = (await ApiService.fnSubmitPlayTime(
'VIDEOCOURSEWARE_ID': _currentVideoData!['VIDEOCOURSEWARE_ID'] ?? '', _currentVideoData!['VIDEOCOURSEWARE_ID'],
'CURRICULUM_ID': _currentVideoData!['CURRICULUM_ID'] ?? '', _currentVideoData!['CURRICULUM_ID'],
'CHAPTER_ID': _currentVideoData!['CHAPTER_ID'] ?? '', end ? '1' : '0',
'RESOURCETIME': seconds, seconds,
'IS_END': end ? '1' : '0', _currentVideoData!['CHAPTER_ID'],
'CLASS_ID': _classId, widget.studentId,
'CLASSCURRICULUM_ID': _classCurriculumId, _classCurriculumId,
'STUDENT_ID': widget.studentId, _classId,
'loading': false, ));
};
final resData = await ApiService.fnSubmitPlayTime(data);
final pd = resData['pd'] ?? {}; final pd = resData['pd'] ?? {};
// //
final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 0; final comp = pd['PLAYCOUNT'] != null && pd['PLAYCOUNT'] > 0;
final resT = pd['RESOURCETIME'] ?? seconds; final resT = pd['RESOURCETIME'] ?? seconds;
final videoTimeRaw = _currentVideoData!['VIDEOTIME']; final pct = comp
final videoTime = ? 100
(videoTimeRaw is String) : (resT / (_currentVideoData!['VIDEOTIME'] ?? 1) * 100)
? double.tryParse(videoTimeRaw) ?? 1 .clamp(0, 100);
: (videoTimeRaw is num ? videoTimeRaw.toDouble() : 1);
final pct = comp ? 100 : (resT / videoTime * 100).clamp(0, 100);
final str = '${pct.floor()}%'; final str = '${pct.floor()}%';
setState(() { setState(() {
if (_hasNodes) { if (_hasNodes) {
_videoList[_currentFirstIndex]['nodes'][_currentNodeIndex]['percent'] = _videoList[_currentFirstIndex]['nodes'][_currentNodeIndex]
str; ['percent'] = str;
} else { } else {
_videoList[_currentFirstIndex]['percent'] = str; _videoList[_currentFirstIndex]['percent'] = str;
} }
@ -315,11 +304,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
// //
if (end && pd['CANEXAM'] == '1') { if (end && pd['CANEXAM'] == '1') {
_videoController?.pause(); _videoController?.pause();
final ok = final ok = await showDialog<bool>(
await showDialog<bool>(
context: context, context: context,
builder: builder: (_) => CustomAlertDialog(
(_) => CustomAlertDialog(
title: '提示', title: '提示',
content: '当前任务内所有课程均已学完,是否直接参加考试?', content: '当前任务内所有课程均已学完,是否直接参加考试?',
confirmText: '', confirmText: '',
@ -328,6 +315,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
) ?? ) ??
false; false;
if (ok) { if (ok) {
_startExam(resData); _startExam(resData);
} else { } else {
_videoController?.play(); _videoController?.play();
@ -354,7 +342,7 @@ class _StudyDetailPageState extends State<StudyDetailPage>
'CLASS_ID': _classId, 'CLASS_ID': _classId,
'POST_ID': pd['POST_ID'] ?? '', 'POST_ID': pd['POST_ID'] ?? '',
'STUDENT_ID': widget.studentId, 'STUDENT_ID': widget.studentId,
'NUMBEROFEXAMS': pd['NUMBEROFEXAMS'] ?? '', 'NUMBEROFEXAMS': pd['NUMBEROFEXAMS'] ?? ''
}; };
print('--_startExam data---$arguments'); print('--_startExam data---$arguments');
@ -363,20 +351,14 @@ class _StudyDetailPageState extends State<StudyDetailPage>
_loading = false; _loading = false;
}); });
if (data['result'] == 'success') { if (data['result'] == 'success') {
pushPage( pushPage(TakeExamPage(examInfo: {
TakeExamPage(
examInfo: {
'CLASS_ID':_classId, 'CLASS_ID':_classId,
'POST_ID': pd['POST_ID'] ?? '', 'POST_ID': pd['POST_ID'] ?? '',
'STUDENT_ID': widget.studentId, 'STUDENT_ID': widget.studentId,
'STRENGTHEN_PAPER_QUESTION_ID': 'STRENGTHEN_PAPER_QUESTION_ID': paper['STAGEEXAMPAPERINPUT_ID']??'',
paper['STAGEEXAMPAPERINPUT_ID'] ?? '', ...data
...data, }, examType: TakeExamType.video_study), context);
},
examType: TakeExamType.video_study,
),
context,
);
}else{ }else{
ToastUtil.showError(context, '请求错误'); ToastUtil.showError(context, '请求错误');
} }
@ -409,27 +391,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
} }
} }
Widget _buildVideoOrCover(double containerW, double containerH) {
final c = _videoController; void _controllerListener() {
if (c != null && c.value.isInitialized) { if (mounted) setState(() {});
return VideoPlayerWidget(
allowSeek: false,
controller: _videoController,
coverUrl:
_videoCoverUrl.isNotEmpty
? ApiService.baseImgPath + _videoCoverUrl
: ApiService.baseImgPath + (_info?['COVERPATH'] ?? ''),
aspectRatio: _videoController?.value.aspectRatio ?? 16 / 9,
);
} else {
// controller
return Image.network(
'${ApiService.baseImgPath}${_info?['COVERPATH'] ?? ''}',
fit: BoxFit.fill,
width: containerW,
height: containerH,
);
}
} }
@override @override
@ -446,8 +410,18 @@ class _StudyDetailPageState extends State<StudyDetailPage>
body: SafeArea( body: SafeArea(
child: Column( child: Column(
children: [ children: [
_buildVideoOrCover(screenWidth(context), 250), SizedBox(
const SizedBox(height: 5,), height: 250,
width: screenWidth(context),
child: VideoPlayerWidget(
allowSeek: false,
controller: _videoController,
coverUrl: _videoCoverUrl.isNotEmpty
? ApiService.baseImgPath + _videoCoverUrl
: ApiService.baseImgPath + (info['COVERPATH'] ?? ''),
aspectRatio: _videoController?.value.aspectRatio ?? 16/9,
),
),
Container( Container(
width: double.infinity, width: double.infinity,
color: Colors.white, color: Colors.white,
@ -493,31 +467,29 @@ class _StudyDetailPageState extends State<StudyDetailPage>
final item = _videoList[idx] as Map<String, dynamic>; final item = _videoList[idx] as Map<String, dynamic>;
final nodes = item['nodes'] as List<dynamic>?; final nodes = item['nodes'] as List<dynamic>?;
if (nodes != null && nodes.isNotEmpty) { if (nodes != null && nodes.isNotEmpty) {
// + return ExpansionTile(
return Column( title: Text(item['NAME'] ?? ''),
crossAxisAlignment: CrossAxisAlignment.start, children:
children: [ nodes
...nodes.asMap().entries.map( .asMap()
.entries
.map(
(e) => _buildVideoItem( (e) => _buildVideoItem(
item,
e.value as Map<String, dynamic>, e.value as Map<String, dynamic>,
true, true,
idx, idx,
e.key, e.key,
), ),
), )
const Divider(height: 1), // 线 .toList(),
],
); );
} }
// return _buildVideoItem(item, false, idx, 0);
return _buildVideoItem(item, item, false, idx, 0);
}, },
); );
} }
Widget _buildVideoItem( Widget _buildVideoItem(
Map<String, dynamic> item,
Map<String, dynamic> m, Map<String, dynamic> m,
bool hasNodes, bool hasNodes,
int fi, int fi,
@ -532,10 +504,9 @@ class _StudyDetailPageState extends State<StudyDetailPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Icon(Icons.file_copy_rounded, color: Colors.grey, size: 20), const Icon(Icons.file_copy_rounded, color: Colors.grey, size: 20),
const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
item['NAME'] ?? '', m['NAME'] ?? '',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
), ),
@ -569,15 +540,12 @@ class _StudyDetailPageState extends State<StudyDetailPage>
), ),
if (m['IS_VIDEO'] == 0) ...[ if (m['IS_VIDEO'] == 0) ...[
Text(secondsCount(m['VIDEOTIME'])), Text(secondsCount(m['VIDEOTIME'])),
const SizedBox(width: 6),
const Icon(Icons.play_circle, color: Colors.blue), const Icon(Icons.play_circle, color: Colors.blue),
], ],
CustomButton( CustomButton(
onPressed: onPressed:
() => pushPage( () => pushPage(
StudyPractisePage( StudyPractisePage(videoCoursewareId: m['VIDEOCOURSEWARE_ID']),
videoCoursewareId: m['VIDEOCOURSEWARE_ID'],
),
context, context,
), ),
text: "课后练习", text: "课后练习",

View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/main.dart';
import 'package:qhd_prevention/pages/home/study/strengthen_video_study_page.dart'; import 'package:qhd_prevention/pages/home/study/strengthen_video_study_page.dart';
import 'package:qhd_prevention/pages/home/study/study_class_list_page.dart'; import 'package:qhd_prevention/pages/home/study/study_class_list_page.dart';
import 'package:qhd_prevention/pages/home/study/study_detail_page.dart'; import 'package:qhd_prevention/pages/home/study/study_detail_page.dart';
@ -22,7 +21,7 @@ class StudyMyTaskPage extends StatefulWidget {
State<StudyMyTaskPage> createState() => _StudyMyTaskPageState(); State<StudyMyTaskPage> createState() => _StudyMyTaskPageState();
} }
class _StudyMyTaskPageState extends State<StudyMyTaskPage> with RouteAware { class _StudyMyTaskPageState extends State<StudyMyTaskPage> {
int _page = 1; int _page = 1;
final int _showCount = 10; final int _showCount = 10;
bool _isLoading = false; bool _isLoading = false;
@ -30,60 +29,29 @@ class _StudyMyTaskPageState extends State<StudyMyTaskPage> with RouteAware {
int _totalPage = 1; int _totalPage = 1;
List<dynamic> _list = []; List<dynamic> _list = [];
Timer? _timer; Timer? _timer;
late DateTime _now; //
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_now = DateTime.now();
WidgetsBinding.instance.addPostFrameCallback((_) => _getStudyList()); WidgetsBinding.instance.addPostFrameCallback((_) => _getStudyList());
} _startCountdownTimer();
@override
void didChangeDependencies() {
super.didChangeDependencies();
final modalRoute = ModalRoute.of(context);
if (modalRoute is PageRoute) {
routeObserver.subscribe(this, modalRoute);
}
} }
///
@override
void didPopNext() {
if (!_isLoading) _getStudyList();
}
@override
void didPush() {
// initState _getStudyList
if (!_isLoading) _getStudyList();
}
@override @override
void dispose() { void dispose() {
_timer?.cancel(); _timer?.cancel();
// 退 routeObserver
try {
routeObserver.unsubscribe(this);
} catch (_) {}
super.dispose(); super.dispose();
} }
/// remainingSeconds /// remainingSeconds
void _startCountdownTimer() { void _startCountdownTimer() {
if (_timer != null && _timer!.isActive) return; //
_timer = Timer.periodic(Duration(seconds: 1), (_) { _timer = Timer.periodic(Duration(seconds: 1), (_) {
setState(() { setState(() {
for (var i = 0; i < _list.length; i++) { for (var item in _list) {
final endTimeStr = _list[i]["END_TIME"] as String; final rs = item['remainingSeconds'] as int? ?? 0;
item['remainingSeconds'] = rs > 0 ? rs - 1 : 0;
try {
//
final endTime = DateTime.parse(endTimeStr);
final now = DateTime.now();
final seconds = endTime.difference(now).inSeconds;
_list[i]['remainingSeconds'] = seconds > 0 ? seconds : 0;
} catch (e) {
_list[i]['remainingSeconds'] = 0;
}
} }
}); });
}); });
@ -122,8 +90,6 @@ class _StudyMyTaskPageState extends State<StudyMyTaskPage> with RouteAware {
} }
_hasMore = _page < _totalPage; _hasMore = _page < _totalPage;
if (_hasMore) _page++; if (_hasMore) _page++;
_startCountdownTimer();
}); });
} }
} catch (e) { } catch (e) {
@ -215,7 +181,7 @@ class _StudyMyTaskPageState extends State<StudyMyTaskPage> with RouteAware {
} }
Widget _buildItem(Map item) { Widget _buildItem(Map item) {
final now = DateTime.now(); final now = _now;
final start = DateTime.tryParse(item['START_TIME'] ?? ''); final start = DateTime.tryParse(item['START_TIME'] ?? '');
final end = DateTime.tryParse(item['END_TIME'] ?? ''); final end = DateTime.tryParse(item['END_TIME'] ?? '');
final nowOk = final nowOk =
@ -249,7 +215,6 @@ class _StudyMyTaskPageState extends State<StudyMyTaskPage> with RouteAware {
// + // +
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
@ -257,7 +222,6 @@ class _StudyMyTaskPageState extends State<StudyMyTaskPage> with RouteAware {
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
), ),
const SizedBox(width: 10,),
Text( Text(
_stateText(item['STUDYSTATE']), _stateText(item['STUDYSTATE']),
style: TextStyle(color: _stateColor(item['STUDYSTATE'])), style: TextStyle(color: _stateColor(item['STUDYSTATE'])),

View File

@ -188,23 +188,17 @@ class _PracticePageState extends State<StudyPractisePage> {
}) { }) {
Color fg = Colors.black87; Color fg = Colors.black87;
Color bg = Colors.grey.shade200; Color bg = Colors.grey.shade200;
Color hasTextColor = Colors.black54;
if (right) { if (right) {
fg = Colors.green; fg = Colors.green;
bg = Colors.green; bg = Colors.green;
hasTextColor = Colors.white;
} }
if (err) { if (err) {
fg = Colors.red; fg = Colors.red;
bg = Colors.red; bg = Colors.red;
hasTextColor = Colors.white;
} }
if (warning) { if (warning) {
fg = Colors.green; fg = Colors.green;
bg = Colors.green; bg = Colors.green;
hasTextColor = Colors.white;
} }
if (active) fg = Colors.blue; if (active) fg = Colors.blue;
@ -232,7 +226,7 @@ class _PracticePageState extends State<StudyPractisePage> {
: Text(label, style: TextStyle(color: fg))) : Text(label, style: TextStyle(color: fg)))
: Text( : Text(
label, label,
style: TextStyle(color: multiple ? fg : hasTextColor), style: TextStyle(color: multiple ? fg : Colors.white),
), ),
), ),
SizedBox(width: 16), SizedBox(width: 16),
@ -255,7 +249,6 @@ class _PracticePageState extends State<StudyPractisePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final q = options.isNotEmpty ? options[current] : null; final q = options.isNotEmpty ? options[current] : null;
return Scaffold( return Scaffold(
backgroundColor: Colors.white,
appBar: MyAppbar(title: '课后练习'), appBar: MyAppbar(title: '课后练习'),
body: body:
loading loading

View File

@ -82,25 +82,13 @@ class _TakeExamPageState extends State<TakeExamPage> {
.map((e) => Question.fromJson(e as Map<String, dynamic>)) .map((e) => Question.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
final numberOfExams = widget.examInfo['NUMBEROFEXAMS']; final numberOfExams = widget.examInfo['NUMBEROFEXAMS'] as String? ?? '0';
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (numberOfExams is int) {
if (numberOfExams > 0) {
}else if (numberOfExams == -9999) {
_showTip('强化学习考试开始,限时${info['ANSWERSHEETTIME']}分钟,请注意答题时间!');
}else{
_showTip('您无考试次数!');
}
}else if (numberOfExams is String) {
if (numberOfExams == '-9999') { if (numberOfExams == '-9999') {
_showTip('强化学习考试开始,限时${info['ANSWERSHEETTIME']}分钟,请注意答题时间!'); _showTip('强化学习考试开始,限时${info['ANSWERSHEETTIME']}分钟,请注意答题时间!');
}
} else { } else {
_showTip('您无考试次数!'); _showTip('您无考试次数!');
} }
}); });
final minutes = info['ANSWERSHEETTIME'] as int? ?? 0; final minutes = info['ANSWERSHEETTIME'] as int? ?? 0;
@ -307,8 +295,8 @@ class _TakeExamPageState extends State<TakeExamPage> {
final q = questions.isNotEmpty ? questions[current] : null; final q = questions.isNotEmpty ? questions[current] : null;
return PopScope( return PopScope(
canPop: false, // canPop: false, //
child: Scaffold( child: Scaffold(
backgroundColor: Colors.white,
appBar: const MyAppbar(title: '课程考试', isBack: false,), appBar: const MyAppbar(title: '课程考试', isBack: false,),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),

View File

@ -26,7 +26,6 @@ class ItemListWidget {
bool strongRequired = false, bool strongRequired = false,
ValueChanged<String>? onChanged, ValueChanged<String>? onChanged,
ValueChanged<String>? onFieldSubmitted, ValueChanged<String>? onFieldSubmitted,
int maxLines = 5,
/// ///
TextInputType keyboardType = TextInputType.text, TextInputType keyboardType = TextInputType.text,
@ -72,7 +71,7 @@ class ItemListWidget {
) )
: Expanded(child: Text( : Expanded(child: Text(
text ?? '', text ?? '',
maxLines: maxLines, maxLines: 5,
style: TextStyle(fontSize: fontSize, color: detailtextColor), style: TextStyle(fontSize: fontSize, color: detailtextColor),
textAlign: TextAlign.right, textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis, // overflow: TextOverflow.ellipsis, //
@ -123,7 +122,7 @@ class ItemListWidget {
isEditable isEditable
? TextFormField( ? TextFormField(
autofocus: false, autofocus: false,
initialValue: controller == null ? text : null, initialValue: text,
controller: controller, controller: controller,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: null, maxLines: null,
@ -133,6 +132,7 @@ class ItemListWidget {
textAlignVertical: TextAlignVertical.top, textAlignVertical: TextAlignVertical.top,
style: TextStyle(fontSize: fontSize), style: TextStyle(fontSize: fontSize),
decoration: InputDecoration( decoration: InputDecoration(
hintText: hintText, hintText: hintText,
// TextField // TextField
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,

View File

@ -1,8 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart';
import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/customWidget/date_picker_dialog.dart'; import 'package:qhd_prevention/customWidget/date_picker_dialog.dart';
@ -22,8 +20,8 @@ class MeasuresListWidget extends StatelessWidget {
required this.isAllowEdit, required this.isAllowEdit,
this.onSign, this.onSign,
this.isShowSign = true, this.isShowSign = true,
});
});
/// Map /// Map
final List<Map<String, dynamic>> measuresList; final List<Map<String, dynamic>> measuresList;
@ -39,6 +37,7 @@ class MeasuresListWidget extends StatelessWidget {
/// ///
final bool isShowSign; final bool isShowSign;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (measuresList.isEmpty) { if (measuresList.isEmpty) {
@ -58,9 +57,8 @@ class MeasuresListWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Table( child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle, defaultVerticalAlignment: TableCellVerticalAlignment.middle, // .top / .bottom
// .top / .bottom
columnWidths: const { columnWidths: const {
0: FlexColumnWidth(3), 0: FlexColumnWidth(3),
1: FixedColumnWidth(100), 1: FixedColumnWidth(100),
@ -97,6 +95,7 @@ class MeasuresListWidget extends StatelessWidget {
// //
for (var item in measuresList) for (var item in measuresList)
TableRow( TableRow(
children: [ children: [
// + + // + +
Padding( Padding(
@ -118,8 +117,7 @@ class MeasuresListWidget extends StatelessWidget {
// 14 + // 14 +
for (var i = 1; i <= 4; i++) for (var i = 1; i <= 4; i++)
if ((item['QUESTION$i'] as String?)?.isNotEmpty ?? if ((item['QUESTION$i'] as String?)?.isNotEmpty ?? false)
false)
_buildQnA(item, i), _buildQnA(item, i),
], ],
), ),
@ -137,16 +135,11 @@ class MeasuresListWidget extends StatelessWidget {
onSign?.call(item); onSign?.call(item);
}, },
child: Text( child: Text(
(item['SIGN_ITEM'] ?? '') (item['SIGN_ITEM'] ?? '').toString().isNotEmpty
.toString()
.isNotEmpty
? '已签字' ? '已签字'
: '签字', : '签字',
style: TextStyle( style: TextStyle(
color: color: (item['SIGN_ITEM'] ?? '').toString().isNotEmpty
(item['SIGN_ITEM'] ?? '')
.toString()
.isNotEmpty
? Colors.grey.shade600 ? Colors.grey.shade600
: Colors.blue, : Colors.blue,
), ),
@ -157,8 +150,7 @@ class MeasuresListWidget extends StatelessWidget {
? '不涉及' ? '不涉及'
: '涉及', : '涉及',
style: TextStyle( style: TextStyle(
color: color: (item['STATUS'] as String?) == '-1'
(item['STATUS'] as String?) == '-1'
? Colors.black ? Colors.black
: Colors.black, : Colors.black,
), ),
@ -166,13 +158,13 @@ class MeasuresListWidget extends StatelessWidget {
// //
if (item.containsKey('IMG_PATH') && if (item.containsKey('IMG_PATH') &&
(item['IMG_PATH'] as String).isNotEmpty && (item['IMG_PATH'] as String).isNotEmpty && isShowSign)
isShowSign)
..._buildImageRows( ..._buildImageRows(
context, context,
(item['IMG_PATH'] as String).split(','), (item['IMG_PATH'] as String).split(','),
'', '',
), ),
], ],
), ),
), ),
@ -208,16 +200,12 @@ class MeasuresListWidget extends StatelessWidget {
flex: 1, flex: 1,
child: TextFormField( child: TextFormField(
initialValue: answer, initialValue: answer,
textAlign: TextAlign.center, textAlign: TextAlign.center, //
//
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
vertical: 8,
horizontal: 4,
),
// //
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey.shade300), borderSide: BorderSide(color: Colors.grey.shade300),
@ -275,10 +263,8 @@ class MeasuresListWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
presentOpaque( presentOpaque(SingleImageViewer(imageUrl: '$baseImgPath$p'), context);
SingleImageViewer(imageUrl: '$baseImgPath$p'),
context,
);
}, },
child: Image.network('$baseImgPath$p', width: 60, height: 60), child: Image.network('$baseImgPath$p', width: 60, height: 60),
), ),
@ -411,10 +397,7 @@ class OtherMeasuresWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
presentOpaque( presentOpaque(SingleImageViewer(imageUrl: '$baseImgPath$path'), context);
SingleImageViewer(imageUrl: '$baseImgPath$path'),
context,
);
}, },
child: Image.network( child: Image.network(
'$baseImgPath$path', '$baseImgPath$path',
@ -619,12 +602,9 @@ class SignaturesListWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: onTap:
() => presentOpaque( () => presentOpaque(SingleImageViewer(
SingleImageViewer(
imageUrl: '$baseImgPath${signPaths[i]}', imageUrl: '$baseImgPath${signPaths[i]}',
), ), context),
context,
),
child: Image.network( child: Image.network(
'$baseImgPath${signPaths[i]}', '$baseImgPath${signPaths[i]}',
width: 100, width: 100,
@ -713,12 +693,9 @@ class SignaturesListWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: onTap:
() => presentOpaque( () => presentOpaque(SingleImageViewer(
SingleImageViewer(
imageUrl: '$baseImgPath${signPaths[i]}', imageUrl: '$baseImgPath${signPaths[i]}',
), ), context),
context,
),
child: Image.network( child: Image.network(
'$baseImgPath${signPaths[i]}', '$baseImgPath${signPaths[i]}',
width: 100, width: 100,
@ -761,7 +738,6 @@ class SelectionPopup extends StatefulWidget {
@override @override
_SelectionPopupState createState() => _SelectionPopupState(); _SelectionPopupState createState() => _SelectionPopupState();
} }
class _SelectionPopupState extends State<SelectionPopup> { class _SelectionPopupState extends State<SelectionPopup> {
late List<Map<String, String>> workList; late List<Map<String, String>> workList;
late String selectedWorkType; late String selectedWorkType;
@ -775,7 +751,7 @@ class _SelectionPopupState extends State<SelectionPopup> {
super.initState(); super.initState();
// //
workList = [ workList = [
{'WORK_TYPE': '', 'WORK_NAME': '选择'}, {'WORK_TYPE': '', 'WORK_NAME': '作业选择'},
{'WORK_TYPE': 'HOTWORK', 'WORK_NAME': '动火作业'}, {'WORK_TYPE': 'HOTWORK', 'WORK_NAME': '动火作业'},
{'WORK_TYPE': 'CONFINEDSPACE', 'WORK_NAME': '受限作业'}, {'WORK_TYPE': 'CONFINEDSPACE', 'WORK_NAME': '受限作业'},
{'WORK_TYPE': 'HIGHWORK', 'WORK_NAME': '高处作业'}, {'WORK_TYPE': 'HIGHWORK', 'WORK_NAME': '高处作业'},
@ -796,17 +772,20 @@ class _SelectionPopupState extends State<SelectionPopup> {
} }
Future<void> _pickDate() async { Future<void> _pickDate() async {
DateTime? picked = await BottomDateTimePicker.showDate( showDialog(
context, context: context,
mode: BottomPickerMode.date, // BottomPickerMode.dateTime builder:
allowFuture: true, (_) => HDatePickerDialog(
minTimeStr: '0-0-0 00:00', initialDate: DateTime.now(),
); onCancel: () => Navigator.of(context).pop(),
if (picked != null) { onConfirm: (selected) {
Navigator.of(context).pop();
setState(() { setState(() {
selectedDate = picked; selectedDate = selected;
}); });
} },
),
);
} }
Future<void> _getData() async { Future<void> _getData() async {
@ -816,9 +795,7 @@ class _SelectionPopupState extends State<SelectionPopup> {
params = { params = {
'WORK_TYPE': selectedWorkType, 'WORK_TYPE': selectedWorkType,
'KEYWORDS': 'KEYWORDS':
selectedDate == null selectedDate == null ? '' : selectedDate!.toString().split(' ')[0],
? ''
: DateFormat('yyyy-MM-dd').format(selectedDate!),
'CORPINFO_ID': SessionService.instance.corpinfoId, 'CORPINFO_ID': SessionService.instance.corpinfoId,
}; };
} else { } else {
@ -880,77 +857,46 @@ class _SelectionPopupState extends State<SelectionPopup> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5), //
),
backgroundColor: Colors.white, backgroundColor: Colors.white,
insetPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 40), insetPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 40),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
height: screenHeight(context), height: 800,
child: Column( child: Column(
children: [ children: [
// //
if (widget.type == 'assignments') if (widget.type == 'assignments')
Padding( Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Column( child: Row(
children: [
Row(
children: [ children: [
// //
Text('作业类型:'),
Expanded( Expanded(
child: DropdownButton<String>( child: DropdownButton<String>(
dropdownColor: Colors.white, dropdownColor: Colors.white,
style: TextStyle(),
isExpanded: true, isExpanded: true,
value: selectedWorkName, value: selectedWorkName,
//
items: items:
workList workList
.map( .map(
(e) => DropdownMenuItem<String>( (e) => DropdownMenuItem(
value: e['WORK_NAME'], value: e['WORK_NAME'],
child: Center(
child: Text( child: Text(
e['WORK_NAME'] ?? '', e['WORK_NAME']!,
style: TextStyle( style: TextStyle(color: Colors.black87),
color: Colors.black87,
fontSize: 15,
),
textAlign: TextAlign.center,
),
), ),
), ),
) )
.toList(), .toList(),
// selectedItemBuilder
selectedItemBuilder: (BuildContext context) {
return workList.map<Widget>((e) {
return Center(
child: Text(
e['WORK_NAME'] ?? '',
style: TextStyle(
color: Colors.black87,
fontSize: 15,
),
textAlign: TextAlign.center,
),
);
}).toList();
},
onChanged: (v) { onChanged: (v) {
final idx = workList.indexWhere( final idx = workList.indexWhere(
(e) => e['WORK_NAME'] == v, (e) => e['WORK_NAME'] == v,
); );
if (idx >= 0) { if (idx >= 0) {
setState(() { setState(() {
selectedWorkType = selectedWorkType = workList[idx]['WORK_TYPE']!;
workList[idx]['WORK_TYPE']!;
selectedWorkName = v!; selectedWorkName = v!;
}); });
_getData(); _getData();
@ -959,6 +905,7 @@ class _SelectionPopupState extends State<SelectionPopup> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
TextButton( TextButton(
onPressed: _pickDate, onPressed: _pickDate,
child: Row( child: Row(
@ -978,39 +925,19 @@ class _SelectionPopupState extends State<SelectionPopup> {
], ],
), ),
), ),
],
),
Row(
children: [
// //
Expanded( CustomButton(
child: CustomButton(
text: '搜索',
padding: EdgeInsets.symmetric(horizontal: 15),
margin: const EdgeInsets.symmetric(horizontal: 0),
height: 35,
backgroundColor: Colors.green,
onPressed: _getData,
),
),
const SizedBox(width: 10,),
//
Expanded(
child: CustomButton(
text: '清空', text: '清空',
margin: const EdgeInsets.symmetric(horizontal: 0),
textStyle: TextStyle(color: Colors.black),
padding: EdgeInsets.symmetric(horizontal: 15), padding: EdgeInsets.symmetric(horizontal: 15),
height: 35, height: 35,
backgroundColor: Colors.grey.shade200, backgroundColor: Colors.blue,
onPressed: _reset, onPressed: _reset,
), ),
),
],
),
], ],
), ),
), ),
const Divider(),
// //
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
@ -1025,17 +952,15 @@ class _SelectionPopupState extends State<SelectionPopup> {
: item['NAME'] as String? ?? ''; : item['NAME'] as String? ?? '';
final checked = value.contains(key); final checked = value.contains(key);
return CheckboxListTile( return CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading, // <--
contentPadding: EdgeInsets.zero, // padding使
activeColor: Colors.blue, activeColor: Colors.blue,
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(key, style: TextStyle(fontSize: 15)), Text(key),
if (widget.type == 'assignments') ...[ if (widget.type == 'assignments') ...[
Text('作业内容: ${item['WORK_CONTENT'] ?? ''}', style: TextStyle(fontSize: 15),), Text('作业内容: ${item['WORK_CONTENT'] ?? ''}'),
Text('作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}', style: TextStyle(fontSize: 15)), Text('作业负责人: ${item['CONFIRM_USER_NAME'] ?? ''}'),
Text('作业申请时间: ${item['CREATTIME'] ?? ''}', style: TextStyle(fontSize: 15)), Text('作业申请时间: ${item['CREATTIME'] ?? ''}'),
], ],
], ],
), ),
@ -1049,7 +974,6 @@ class _SelectionPopupState extends State<SelectionPopup> {
}); });
}, },
); );
}, },
), ),
), ),
@ -1085,3 +1009,5 @@ class _SelectionPopupState extends State<SelectionPopup> {
); );
} }
} }

View File

@ -119,7 +119,6 @@ class _HotWorkDetailFormWidgetState extends State<HotWorkDetailFormWidget> {
controller: widget.contentController, controller: widget.contentController,
text: pd['WORK_CONTENT'] ?? '', text: pd['WORK_CONTENT'] ?? '',
), ),
const Divider(), const Divider(),
ItemListWidget.singleLineTitleText( ItemListWidget.singleLineTitleText(
label: '动火地点及动火部位:', label: '动火地点及动火部位:',

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:encrypt/encrypt.dart' as encrypt; import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey; import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey;
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/tools/tools.dart'; import 'package:qhd_prevention/tools/tools.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
@ -31,8 +32,8 @@ class AuthService {
final data = await ApiService.loginCheck(encrypted); final data = await ApiService.loginCheck(encrypted);
final result = data['result'] as String? ?? ''; final result = data['result'] as String? ?? '';
if (result != 'success'){ if (result != 'success'){
Fluttertoast.showToast(msg: data['msg'] ?? ''); Fluttertoast.showToast(msg:data['msg']);
// ToastUtil.showNormal(context,data['msg']);
return false; return false;
} }

View File

@ -1,47 +0,0 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:video_compress/video_compress.dart';
class VideoConverter {
/// mp4 mp4
static Future<String> convertToMp4(String inputPath) async {
final ext = path.extension(inputPath).toLowerCase();
// mp4
if (ext == '.mp4') {
return inputPath;
}
try {
print('开始转换: $inputPath');
// + mp4
final MediaInfo? info = await VideoCompress.compressVideo(
inputPath,
quality: VideoQuality.DefaultQuality, // : Low, Medium, High
deleteOrigin: false, //
includeAudio: true,
);
if (info == null || info.path == null) {
throw Exception('视频转换失败: $inputPath');
}
print('转换完成: ${info.path}');
return info.path!;
} catch (e) {
print('视频转换出错: $e');
rethrow;
}
}
/// mp4
static Future<List<String>> convertAllToMp4(List<String> videoPaths) async {
final results = <String>[];
for (final path in videoPaths) {
final newPath = await convertToMp4(path);
results.add(newPath);
}
return results;
}
}

View File

@ -16,14 +16,11 @@ int getRandomWithNum(int min, int max) {
return random.nextInt(max - min + 1) + min; // [min, max] return random.nextInt(max - min + 1) + min; // [min, max]
} }
double screenHeight(BuildContext context) {
double screenHeight = MediaQuery.of(context).size.height;
return screenHeight;
}
double screenWidth(BuildContext context) { double screenWidth(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width; double screenWidth = MediaQuery.of(context).size.width;
return screenWidth; return screenWidth;
} }
Future<T?> pushPage<T>(Widget page, BuildContext context) { Future<T?> pushPage<T>(Widget page, BuildContext context) {
return Navigator.push<T>( return Navigator.push<T>(
context, context,
@ -197,8 +194,6 @@ class SessionService {
String? customRecordDangerJson; String? customRecordDangerJson;
String? unqualifiedInspectionItemID; String? unqualifiedInspectionItemID;
String? listItemNameJson; String? listItemNameJson;
String? studyToken;
/// ///
@ -210,7 +205,6 @@ class SessionService {
// setters // setters
void setLoginUser(Map<String, dynamic> user) => loginUser = user; void setLoginUser(Map<String, dynamic> user) => loginUser = user;
void setStudyToken(String token) => studyToken = token;
void setLoginUserId(String id) => loginUserId = id; void setLoginUserId(String id) => loginUserId = id;

File diff suppressed because it is too large Load Diff

View File

@ -106,8 +106,7 @@ dependencies:
#百度地图 #百度地图
flutter_baidu_mapapi_base: ^3.9.5 flutter_baidu_mapapi_base: ^3.9.5
flutter_baidu_mapapi_map: ^3.9.5 flutter_baidu_mapapi_map: ^3.9.5
#文件处理
video_compress: ^3.1.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter