nfc巡检暂存

main
hs 2025-08-27 16:14:50 +08:00
parent 566ab85147
commit b5992b94ab
18 changed files with 3383 additions and 1568 deletions

View File

@ -491,9 +491,11 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -504,6 +506,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention; PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -682,9 +685,11 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -695,6 +700,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention; PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -710,9 +716,11 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8AKCJ9LW7D; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8AKCJ9LW7D;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -723,6 +731,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention; PRODUCT_BUNDLE_IDENTIFIER = com.zhuoyun.qhdprevention.qhdPrevention;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "flutter-weihua";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";

View File

@ -1,77 +1,77 @@
import UIKit import UIKit
import Flutter import Flutter
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
// //
static var orientationMask: UIInterfaceOrientationMask = .portrait static var orientationMask: UIInterfaceOrientationMask = .portrait
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
let controller = window?.rootViewController as! FlutterViewController let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "app.orientation", let channel = FlutterMethodChannel(name: "app.orientation",
binaryMessenger: controller.binaryMessenger) binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { [weak self] call, result in channel.setMethodCallHandler { [weak self] call, result in
guard let self = self else { return } guard let self = self else { return }
if call.method == "setOrientation" { if call.method == "setOrientation" {
guard let arg = call.arguments as? String else { guard let arg = call.arguments as? String else {
result(FlutterError(code: "BAD_ARGS", message: "need 'landscape' | 'portrait'", details: nil)) result(FlutterError(code: "BAD_ARGS", message: "need 'landscape' | 'portrait'", details: nil))
return
}
//
if arg == "landscape" {
AppDelegate.orientationMask = .landscape
} else if arg == "portrait" {
AppDelegate.orientationMask = .portrait
} else {
result(FlutterError(code: "BAD_ARGS", message: "unknown arg", details: nil))
return
}
//
if #available(iOS 16.0, *) {
// VC supportedInterfaceOrientations
self.window?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
if let scene = self.window?.windowScene {
let orientations: UIInterfaceOrientationMask =
(arg == "landscape") ? .landscape : .portrait
do {
try scene.requestGeometryUpdate(.iOS(interfaceOrientations: orientations))
} catch {
result(FlutterError(code: "GEOMETRY_UPDATE_FAILED",
message: error.localizedDescription, details: nil))
return return
} }
//
if arg == "landscape" {
AppDelegate.orientationMask = .landscape
} else if arg == "portrait" {
AppDelegate.orientationMask = .portrait
} else {
result(FlutterError(code: "BAD_ARGS", message: "unknown arg", details: nil))
return
}
//
if #available(iOS 16.0, *) {
// VC supportedInterfaceOrientations
self.window?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
if let scene = self.window?.windowScene {
let orientations: UIInterfaceOrientationMask =
(arg == "landscape") ? .landscape : .portrait
do {
try scene.requestGeometryUpdate(.iOS(interfaceOrientations: orientations))
} catch {
result(FlutterError(code: "GEOMETRY_UPDATE_FAILED",
message: error.localizedDescription, details: nil))
return
}
}
} else {
let target: UIInterfaceOrientation =
(arg == "landscape") ? .landscapeLeft : .portrait
UIDevice.current.setValue(target.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
}
result(true)
} else {
result(FlutterMethodNotImplemented)
} }
} else {
let target: UIInterfaceOrientation =
(arg == "landscape") ? .landscapeLeft : .portrait
UIDevice.current.setValue(target.rawValue, forKey: "orientation")
UIViewController.attemptRotationToDeviceOrientation()
} }
result(true) GeneratedPluginRegistrant.register(with: self)
} else { return super.application(application, didFinishLaunchingWithOptions: launchOptions)
result(FlutterMethodNotImplemented) }
//
override func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?)
-> UIInterfaceOrientationMask {
return AppDelegate.orientationMask
} }
} }
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
//
override func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?)
-> UIInterfaceOrientationMask {
return AppDelegate.orientationMask
}
}

View File

@ -1,89 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string> <string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>智守安全</string> <string>智守安全</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/>
<key>NFCReaderUsageDescription</key>
<string>需要NFC权限来读取和写入标签</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/> <true/>
<key>NFCReaderUsageDescription</key>
<string>用于读取 NFC 标签</string>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>app需要蓝牙权限连接设备</string>
<key>NSCameraUsageDescription</key>
<string>app需要相机权限来扫描二维码</string>
<key>NSContactsUsageDescription</key>
<string>app需要通讯录权限添加好友</string>
<key>NSHealthShareUsageDescription</key>
<string>app需要读取健康数据</string>
<key>NSHealthUpdateUsageDescription</key>
<string>app需要写入健康数据</string>
<key>NSLocalNetworkUsageDescription</key>
<string>app需要发现本地网络设备</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>app需要后台定位以实现持续跟踪</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要位置权限以提供定位服务</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要位置权限以提供定位服务</string>
<key>NSMicrophoneUsageDescription</key>
<string>app需要麦克风权限进行语音通话</string>
<key>NSMotionUsageDescription</key>
<string>app需要访问运动数据统计步数</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>app需要保存图片到相册</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>app需要访问相册以上传图片</string>
<key>NSUserNotificationsUsageDescription</key>
<string>app需要发送通知提醒重要信息</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarHidden</key>
<false/>
</dict> </dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>app需要蓝牙权限连接设备</string>
<key>NSCameraUsageDescription</key>
<string>app需要相机权限来扫描二维码</string>
<key>NSContactsUsageDescription</key>
<string>app需要通讯录权限添加好友</string>
<key>NSHealthShareUsageDescription</key>
<string>app需要读取健康数据</string>
<key>NSHealthUpdateUsageDescription</key>
<string>app需要写入健康数据</string>
<key>NSLocalNetworkUsageDescription</key>
<string>app需要发现本地网络设备</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>app需要后台定位以实现持续跟踪</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要位置权限以提供定位服务</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要位置权限以提供定位服务</string>
<key>NSMicrophoneUsageDescription</key>
<string>app需要麦克风权限进行语音通话</string>
<key>NSMotionUsageDescription</key>
<string>app需要访问运动数据统计步数</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>app需要保存图片到相册</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>app需要访问相册以上传图片</string>
<key>NSUserNotificationsUsageDescription</key>
<string>app需要发送通知提醒重要信息</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>NDEF</string>
<string>TAG</string>
</array>
</dict>
</plist> </plist>

View File

@ -3,8 +3,11 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.nfc.readersession.formats</key> <key>com.apple.developer.nfc.readersession.formats</key>
<array> <array>
<string>TAG</string> <string>NDEF</string>
</array> <string>TAG</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// /
///
class CustomButton extends StatelessWidget { class CustomButton extends StatelessWidget {
final String text; // final String text; //
final Color backgroundColor; // final Color backgroundColor; //
@ -11,6 +10,16 @@ class CustomButton extends StatelessWidget {
final double? height; // final double? height; //
final TextStyle? textStyle; // final TextStyle? textStyle; //
/// true false
/// onPressed null
final bool enabled;
///
final Color? disabledBackgroundColor;
///
final Color? disabledTextColor;
const CustomButton({ const CustomButton({
super.key, super.key,
required this.text, required this.text,
@ -21,27 +30,56 @@ class CustomButton extends StatelessWidget {
this.margin, this.margin,
this.height, this.height,
this.textStyle, this.textStyle,
this.enabled = true,
this.disabledBackgroundColor,
this.disabledTextColor,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( // enabled false onPressed null
onTap: onPressed, final bool isEnabled = enabled && onPressed != null;
child: Container(
height: height ?? 45, // 45 //
padding: padding ?? const EdgeInsets.all(8), // final Color bgColor = isEnabled
margin: margin ?? const EdgeInsets.symmetric(horizontal: 5), // ? backgroundColor
decoration: BoxDecoration( : (disabledBackgroundColor ?? Colors.grey.shade400);
borderRadius: BorderRadius.circular(borderRadius),
color: backgroundColor, TextStyle finalTextStyle;
), if (textStyle != null) {
child: Center( finalTextStyle = isEnabled
child: Text( ? textStyle!
text, : textStyle!.copyWith(
style: textStyle ?? const TextStyle( color: disabledTextColor ?? textStyle!.color?.withOpacity(0.8) ?? Colors.white70,
color: Colors.white, );
fontSize: 15, } else {
fontWeight: FontWeight.bold, finalTextStyle = TextStyle(
color: isEnabled ? Colors.white : (disabledTextColor ?? Colors.white70),
fontSize: 15,
fontWeight: FontWeight.bold,
);
}
// +
return Opacity(
opacity: isEnabled ? 1.0 : 0.65,
child: AbsorbPointer(
absorbing: !isEnabled,
child: GestureDetector(
onTap: isEnabled ? onPressed : null,
child: Container(
height: height ?? 45, // 45
padding: padding ?? const EdgeInsets.all(8), //
margin: margin ?? const EdgeInsets.symmetric(horizontal: 5), //
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: bgColor,
),
child: Center(
child: Text(
text,
style: finalTextStyle,
),
), ),
), ),
), ),

View File

@ -20,6 +20,8 @@ class MediaPickerRow extends StatefulWidget {
final ValueChanged<String>? onMediaRemoved; final ValueChanged<String>? onMediaRemoved;
final ValueChanged<String>? onMediaTapped; // final ValueChanged<String>? onMediaTapped; //
final bool isEdit; // final bool isEdit; //
final bool isCamera; //
const MediaPickerRow({ const MediaPickerRow({
Key? key, Key? key,
@ -31,6 +33,7 @@ class MediaPickerRow extends StatefulWidget {
this.onMediaRemoved, this.onMediaRemoved,
this.onMediaTapped, // this.onMediaTapped, //
this.isEdit = true, // this.isEdit = true, //
this.isCamera = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -53,7 +56,16 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
); );
}); });
} }
Future<void> _cameraAction() async {
XFile? picked = await _picker.pickImage(source: ImageSource.camera);
if (picked != null) {
final path = picked.path;
setState(() => _mediaPaths.add(path));
widget.onChanged(_mediaPaths.map((p) => File(p)).toList());
widget.onMediaAdded?.call(path);
}
}
Future<void> _showPickerOptions() async { Future<void> _showPickerOptions() async {
if (!widget.isEdit) return; // if (!widget.isEdit) return; //
@ -237,7 +249,7 @@ class _MediaPickerGridState extends State<MediaPickerRow> {
// //
else if (showAddButton) { else if (showAddButton) {
return GestureDetector( return GestureDetector(
onTap: _showPickerOptions, onTap: widget.isCamera?_cameraAction:_showPickerOptions,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.black12), border: Border.all(color: Colors.black12),
@ -273,6 +285,8 @@ class RepairedPhotoSection extends StatefulWidget {
final bool isRequired; final bool isRequired;
final bool isShowNum; final bool isShowNum;
final bool isEdit; // final bool isEdit; //
final bool isCamera; //
const RepairedPhotoSection({ const RepairedPhotoSection({
Key? key, Key? key,
@ -290,6 +304,7 @@ class RepairedPhotoSection extends StatefulWidget {
this.isRequired = false, this.isRequired = false,
this.isShowNum = true, this.isShowNum = true,
this.isEdit = true, // this.isEdit = true, //
this.isCamera = false,
}) : super(key: key); }) : super(key: key);
@override @override
@ -331,6 +346,7 @@ class _RepairedPhotoSectionState extends State<RepairedPhotoSection> {
maxCount: widget.maxCount, maxCount: widget.maxCount,
mediaType: widget.mediaType, mediaType: widget.mediaType,
initialMediaPaths: _mediaPaths, initialMediaPaths: _mediaPaths,
isCamera: widget.isCamera,
onChanged: (files) { onChanged: (files) {
final newPaths = files.map((f) => f.path).toList(); final newPaths = files.map((f) => f.path).toList();
setState(() { setState(() {

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
// main.dart
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/pages/badge_manager.dart'; import 'package:qhd_prevention/pages/badge_manager.dart';
import 'package:qhd_prevention/services/auth_service.dart'; import 'package:qhd_prevention/services/auth_service.dart';
@ -8,6 +10,7 @@ import './pages/main_tab.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'http/HttpManager.dart'; import 'http/HttpManager.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter/services.dart'; // for TextInput.hide
// //
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@ -28,6 +31,36 @@ class GlobalMessage {
} }
} }
/// helper
Future<T?> showDialogAfterUnfocus<T>(BuildContext context, Widget dialog) async {
//
FocusScope.of(context).unfocus();
try {
await SystemChannels.textInput.invokeMethod('TextInput.hide');
} catch (_) {}
// 100-200ms
await Future.delayed(const Duration(milliseconds: 150));
return showDialog<T>(context: context, builder: (_) => dialog);
}
///
Future<T?> showModalBottomSheetAfterUnfocus<T>({
required BuildContext context,
required WidgetBuilder builder,
bool isScrollControlled = false,
}) async {
FocusScope.of(context).unfocus();
try {
await SystemChannels.textInput.invokeMethod('TextInput.hide');
} catch (_) {}
await Future.delayed(const Duration(milliseconds: 150));
return showModalBottomSheet<T>(
context: context,
isScrollControlled: isScrollControlled,
builder: builder,
);
}
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -70,8 +103,6 @@ void main() async {
// token // token
try { try {
isLoggedIn = await AuthService.isLoggedIn(); isLoggedIn = await AuthService.isLoggedIn();
// AuthService.isLoggedIn()
// false
} catch (e) { } catch (e) {
isLoggedIn = false; isLoggedIn = false;
} }
@ -80,6 +111,7 @@ void main() async {
runApp(MyApp(isLoggedIn: isLoggedIn)); runApp(MyApp(isLoggedIn: isLoggedIn));
} }
/// MyApp Stateless viewInsets
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
final bool isLoggedIn; final bool isLoggedIn;
@ -90,13 +122,15 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: '', title: '',
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
// push/pop TextField
navigatorObservers: [KeyboardUnfocusNavigatorObserver()],
builder: (context, child) { builder: (context, child) {
return EasyLoading.init( return EasyLoading.init(
builder: (context, widget) { builder: (context, widget) {
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () {
// FocusScope.of(context).unfocus(); //
FocusHelper.clearFocus(context); FocusHelper.clearFocus(context);
}, },
child: widget, child: widget,
@ -136,3 +170,38 @@ class MyApp extends StatelessWidget {
); );
} }
} }
/// NavigatorObserver push/pop/remove/replace
class KeyboardUnfocusNavigatorObserver extends NavigatorObserver {
void _unfocus() {
try {
FocusManager.instance.primaryFocus?.unfocus();
} catch (e) {
debugPrint('NavigatorObserver unfocus error: $e');
}
}
@override
void didPush(Route route, Route? previousRoute) {
_unfocus();
super.didPush(route, previousRoute);
}
@override
void didPop(Route route, Route? previousRoute) {
_unfocus();
super.didPop(route, previousRoute);
}
@override
void didRemove(Route route, Route? previousRoute) {
_unfocus();
super.didRemove(route, previousRoute);
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
_unfocus();
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
}
}

View File

@ -526,11 +526,12 @@ class _QuickReportPageState extends State<QuickReportPage> {
String latitude=position.latitude.toString(); String latitude=position.latitude.toString();
try { try {
Map data = {};
final result = await ApiService.addRiskListCheckApp( final result = await ApiService.addRiskListCheckApp(
hazardDescription, partDescription, latitude, longitude, hazardDescription, partDescription, latitude, longitude,
dangerDetail, dataTime, type, responsibleId, dangerDetail, dataTime, type, responsibleId,
yinHuanTypeIds, hazardLeve, buMenId, buMenPDId, yinHuanTypeIds, hazardLeve, buMenId, buMenPDId,
yinHuanTypeNames, hiddenType1, hiddenType2, hiddenType3,); yinHuanTypeNames, hiddenType1, hiddenType2, hiddenType3,data);
if (result['result'] == 'success') { if (result['result'] == 'success') {
String hiddenId = result['pd']['HIDDEN_ID'] ; String hiddenId = result['pd']['HIDDEN_ID'] ;

View File

@ -124,25 +124,51 @@ class _HomeNfcAddPageState extends State<HomeNfcAddPage> {
); );
if (confirmed) { if (confirmed) {
LoadingDialogHelper.show(message: '等待手机靠近NFC标签'); LoadingDialogHelper.show(message: '等待手机靠近NFC标签');
await NfcService.instance.writeText( NfcService.instance.startScanOnceWithCallback(
mapToCompactJson({'PIPELINE_AREA_ID':pd['PIPELINE_AREA_ID'],'EQUIPMENT_PIPELINE_ID':pd['EQUIPMENT_PIPELINE_ID']}), onResult: (uid, parsedText, rawMsg) async {
timeout: Duration(seconds: 12), final result = await ApiService.nfcWriteCheck(uid);
onComplete: (ok, msg) async{ if (result['result'] == 'success') {
if (ok) { _writeNFCInfoRequest();
pd['NFC_CODE'] = msg; }else{
final result = await ApiService.nfcTagAdd(pd); ToastUtil.showError(context, result['result'] ?? '');
if (result['result'] == 'success') { }
ToastUtil.showSuccess(context, '写入成功'); LoadingDialogHelper.hide();
Navigator.pop(context);
}else{
ToastUtil.showError(context, '写入失败,请重试');
}
}
LoadingDialogHelper.hide();
}, },
onError: (err) {
ToastUtil.showNormal(context, '$err');
LoadingDialogHelper.hide();
},
timeout: Duration(seconds: 12),
); );
} }
} }
Future<void> _writeNFCInfoRequest() async{
await NfcService.instance.writeText(
mapToCompactJson({'PIPELINE_AREA_ID':pd['PIPELINE_AREA_ID'],'EQUIPMENT_PIPELINE_ID':pd['EQUIPMENT_PIPELINE_ID']}),
timeout: Duration(seconds: 12),
onComplete: (ok, msg) async{
if (ok) {
pd['NFC_CODE'] = msg;
final result = await ApiService.nfcTagAdd(pd);
if (result['result'] == 'success') {
ToastUtil.showSuccess(context, '写入成功');
Navigator.pop(context);
}else{
ToastUtil.showError(context, '写入失败,请重试');
}
}else{
ToastUtil.showError(context, '$msg');
}
LoadingDialogHelper.hide();
},
);
LoadingDialogHelper.hide();
}
String mapToCompactJson(Map<String, dynamic> map) { String mapToCompactJson(Map<String, dynamic> map) {
// 使 jsonEncode // 使 jsonEncode
String jsonStr = jsonEncode(map); String jsonStr = jsonEncode(map);

View File

@ -1,17 +1,207 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_detail_page.dart';
import 'package:qhd_prevention/pages/home/NFC/nfc_check_danger_detail.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';
import 'package:qhd_prevention/tools/tools.dart';
/// OptionData
class OptionData {
final String value;
final String label;
final IconData icon;
final Color color;
const OptionData({
required this.value,
required this.label,
required this.icon,
required this.color,
});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is OptionData &&
runtimeType == other.runtimeType &&
value == other.value &&
label == other.label &&
icon == other.icon &&
color == other.color;
@override
int get hashCode =>
value.hashCode ^ label.hashCode ^ icon.hashCode ^ color.hashCode;
}
class HomeNfcCheckDangerPage extends StatefulWidget { class HomeNfcCheckDangerPage extends StatefulWidget {
const HomeNfcCheckDangerPage({super.key}); const HomeNfcCheckDangerPage({
super.key,
required this.info,
required this.facebookImages,
required this.isNfcError
});
final Map info;
final List<String> facebookImages;
// nfc
final bool isNfcError;
@override @override
State<HomeNfcCheckDangerPage> createState() => _HomeNfcCheckDangerPageState(); State<HomeNfcCheckDangerPage> createState() => _HomeNfcCheckDangerPageState();
} }
class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> { class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
late Map<String, dynamic> pd = {};
OptionData? selectType; // null
final List<OptionData> _options = const [
OptionData(
value: "option1",
label: "合格",
icon: Icons.check_circle_rounded,
color: Colors.green,
),
OptionData(
value: "option2",
label: "不合格",
icon: Icons.check_circle_rounded,
color: Colors.green,
),
OptionData(
value: "option3",
label: "不涉及",
icon: Icons.check_circle_rounded,
color: Colors.green,
),
];
@override
void initState() {
// TODO: implement initState
super.initState();
final bool unchecked = widget.info['INSPECTED_FLAG'] == '0';
if (!unchecked) { //
for (OptionData data in _options) {
if (data.label == widget.info['INSPECTION_RESULT']) {
selectType = data;
}
}
}
if (selectType != null && selectType?.label == '不合格') { //
_getCheckRecord();
}
Widget _pendingTopCard(Map<String, String> item) { }
Future<void> _getCheckRecord() async{
Map data = {'PATROL_RECORD_DETAIL_ID': widget.info['PATROL_RECORD_DETAIL_ID']};
final result = await ApiService.nfcDangerRecord(data);
if (result['result'] == 'success') {
setState(() {
pd = result['pd'];
});
}
}
Future<void> _submit() async {
//
if (selectType == null) {
//
ToastUtil.showNormal(context, '请先选择检查结果');
return;
}
Map data = {
...widget.info,
'INSPECTION_RESULT': selectType?.label ?? '',
'TYPE': '0', // (0- 1-)
};
LoadingDialogHelper.show();
// ,
if (selectType?.label == '不合格') {
List imgList = pd['imgList'] ?? [];
List _videos = pd['videoList'] ?? [];
List zgImgList = pd['gzImageList'] ?? [];
for (int i = 0; i < imgList.length; i++) {
await _reloadFeedBack(imgList[i], '3');
}
for (int i = 0; i < _videos.length; i++) {
await _reloadFeedBack(_videos[i], '3');
}
for (int i = 0; i < zgImgList.length; i++) {
await _reloadFeedBack(zgImgList[i], '4');
}
data = {...data,...pd};
}
if (widget.facebookImages.isNotEmpty) {
// nfc
final List<String> uploaded = [];
for (int i = 0; i < widget.facebookImages.length; i++) {
String imagePath = await _reloadFeedBack(widget.facebookImages[i], '30');
if (imagePath.isNotEmpty) {
uploaded.add(imagePath);
}
}
if (uploaded.isNotEmpty) {
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();
} else {
//
ToastUtil.showNormal(context, result['result']?.toString() ?? '提交失败');
}
}
Future<String> _reloadFeedBack(String imagePath, String type) async {
try {
Map data = {
'TYPE': type,
'FOREIGN_KEY': widget.info['EQUIPMENT_PIPELINE_ID'],
};
final raw = await ApiService.addNormalImgFiles(imagePath, data);
if (raw['result'] == 'success') {
Map pd = raw['pd'];
return pd['FILEPATH'] ?? "";
} else {
return "";
}
} catch (e) {
// Toast
debugPrint('加载首页数据失败:$e');
return "";
}
}
void _pushDangerDetail() async {
// pushPage pd pd
final result = await pushPage<Map<String, dynamic>>(
NfcCheckDangerDetail(info: pd),
context,
);
if (result != null && result.isNotEmpty) {
setState(() {
//
selectType = _options[1];
pd = result;
});
}
}
Widget _pendingTopCard(Map item) {
return SizedBox( return SizedBox(
height: 180, height: 180,
child: Stack( child: Stack(
@ -32,7 +222,7 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
top: 12, top: 12,
left: 12, left: 12,
child: Text( child: Text(
item['title']!, item['TASK_NAME']?.toString() ?? '',
style: const TextStyle( style: const TextStyle(
color: Colors.black87, color: Colors.black87,
fontSize: 18, fontSize: 18,
@ -52,7 +242,7 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
), ),
child: Center( child: Center(
child: Text( child: Text(
item['status']!, item['PATROL_TYPE_NAME'] ?? '',
style: const TextStyle(color: Colors.white, fontSize: 14), style: const TextStyle(color: Colors.white, fontSize: 14),
), ),
), ),
@ -66,11 +256,11 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
top: 50, // top: 50, //
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 0), margin: const EdgeInsets.symmetric(horizontal: 0),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
boxShadow: [ boxShadow: const [
BoxShadow( BoxShadow(
color: Colors.black12, color: Colors.black12,
blurRadius: 4, blurRadius: 4,
@ -86,32 +276,126 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
); );
} }
/// // option OptionData
Widget _buildInfoGrid(Map<String, String> item) { Widget _buildOptionButton({
return Row( required BuildContext context,
children: [ required OptionData option,
Expanded( required double screenWidth,
child: Column( required dynamic item,
crossAxisAlignment: CrossAxisAlignment.start, VoidCallback? onImageTap,
children: [ }) {
Text('负责部门:${item['department']}'), final String value = option.value;
const SizedBox(height: 8), final String label = option.label;
Text('负责人:${item['owner']}'), final icon = option.icon;
const SizedBox(height: 8), final color = option.color;
Text('UN件类型${item['unType']}'), final bool isSelected = selectType?.value == option.value;
], final buttonWidth = (screenWidth - 60) / 3 - 10; //
),
return GestureDetector(
onTap: () {
setState(() {
if (value != "option2") {
selectType = option;
} else {
//
_pushDangerDetail();
}
});
},
behavior: HitTestBehavior.opaque,
child: Container(
width: buttonWidth,
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
height: 30,
width: 90,
child: Row(
children: [
Icon(icon, color: isSelected ? color : Colors.grey, size: 30),
const SizedBox(width: 8),
Flexible(
child: Text(
label,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? color : Colors.grey[600],
),
),
),
],
),
),
const SizedBox(width: 6),
if ((value == "option1" && item["REFERENCE_BASIS"] == "option1") ||
(value == "option2" &&
item["REFERENCE_BASIS"] == "option2" &&
item.containsKey("ids") &&
item["ids"].toString().isNotEmpty))
GestureDetector(
onTap: () {
if (onImageTap != null) {
onImageTap();
}
},
behavior: HitTestBehavior.opaque,
child: Transform.translate(
offset: const Offset(0, -6),
child: Image.asset(
"assets/images/gantan-blue.png",
width: 15,
height: 15,
),
),
),
],
), ),
Expanded( ),
child: Column( );
crossAxisAlignment: CrossAxisAlignment.end, }
children: [
Text('巡检周期:${item['cycle']}'), ///
const SizedBox(height: 8), Widget _buildInfoGrid(Map item) {
Text('已巡点位:${item['points']}'), final screenWidth = MediaQuery.of(context).size.width;
Text('涉及管道区域:${item['department']}')
], return Column(
), crossAxisAlignment: CrossAxisAlignment.start,
children: [
ItemListWidget.singleLineTitleText(
label: '检查项:',
isEditable: false,
text: item['EQUIPMENT_NAME'] ?? '',
),
ItemListWidget.singleLineTitleText(
label: '检查内容:',
isEditable: false,
text: item['INSPECTION_CONTENT'] ?? '',
),
const SizedBox(height: 8),
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _options.map((option) {
return _buildOptionButton(
context: context,
option: option,
screenWidth: screenWidth,
item: item,
onImageTap: () {
if (item["REFERENCE_BASIS"] == "option1") {
// _getAlreadyUpImages(item);
} else if (item["REFERENCE_BASIS"] == "option2") {
// _goUnqualifiedPage(item);
}
},
);
}).toList(),
), ),
], ],
); );
@ -119,18 +403,28 @@ class _HomeNfcCheckDangerPageState extends State<HomeNfcCheckDangerPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool canSubmit = selectType != null; //
return Scaffold( return Scaffold(
appBar: MyAppbar(title: '检查项'), appBar: MyAppbar(title: '检查项'),
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
padding: EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
_pendingTopCard({}), children: [
] _pendingTopCard(widget.info),
) const Spacer(),
) CustomButton(
) enabled: canSubmit,
text: '提交',
backgroundColor: Colors.blue,
onPressed: canSubmit ? _submit : null,
),
],
),
),
),
); );
} }
} }

View File

@ -2,24 +2,32 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart'; import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_check_danger_page.dart';
import 'package:qhd_prevention/pages/home/NFC/nfc_question_fecebook.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';
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({super.key, required this.info}); const HomeNfcDetailPage({super.key, required this.info});
final Map<String, dynamic> info; final Map info;
@override @override
State<HomeNfcDetailPage> createState() => _HomeNfcDetailPageState(); State<HomeNfcDetailPage> createState() => _HomeNfcDetailPageState();
} }
class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> { class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
List<ProgressItem> items = []; List<dynamic> items = [];
int currentPage = 1; int currentPage = 1;
final int pageSize = 10; final int pageSize = 10;
late var _total = 0;
late var _totalResult = 0;
bool isLoading = false; // bool isLoading = false; //
bool hasMore = true; // bool hasMore = true; //
@ -61,11 +69,15 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
} }
try { try {
// API ApiService.nfcTaskDetailList(pageSize, page) Map data = {
"PATROL_TASK_ID": widget.info['PATROL_TASK_ID'] ?? '',
"PERIOD_START_TIME": widget.info['PERIOD_START_TIME'] ?? '',
"PERIOD_END_TIME": widget.info['PERIOD_END_TIME'] ?? '',
};
final result = await ApiService.nfcTaskDetailList( final result = await ApiService.nfcTaskDetailList(
pageSize, pageSize,
currentPage, currentPage,
widget.info['PATROL_TASK_ID'] ?? '', data,
); );
if (result == null) { if (result == null) {
@ -74,35 +86,20 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
} }
printLongString(jsonEncode(result)); printLongString(jsonEncode(result));
if (result['result'] == 'success') { if (result['result'] == 'success') {
// data / rows / list
final dynamic rawList = result['varList']; final dynamic rawList = result['varList'];
List<ProgressItem> fetched = [];
if (rawList is List) {
fetched =
rawList.map<ProgressItem>((e) {
if (e is ProgressItem) return e;
if (e is Map<String, dynamic>) return ProgressItem.fromJson(e);
return ProgressItem(
status: '未查',
location: e?.toString() ?? '',
code: '',
checkTime: null,
);
}).toList();
}
setState(() { setState(() {
Map page = result['page'];
_totalResult = page['totalResult'] as int;
_total = result['checkedCount'] as int;
if (refresh) { if (refresh) {
items = fetched; items = rawList;
} else { } else {
items.addAll(fetched); items.addAll(rawList);
} }
// pageSize // pageSize
if (fetched.length < pageSize) { if (rawList.length < pageSize) {
hasMore = false; hasMore = false;
} else { } else {
//
currentPage++; currentPage++;
} }
}); });
@ -126,23 +123,74 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
} }
Future<void> _startCheckItem(ProgressItem item, int index) async { Future<void> _startCheckItem(Map item, int index) async {
// TODO: NFC final confirmed = await CustomAlertDialog.showConfirm(
// context,
final now = DateTime.now(); title: '温馨提示',
setState(() { content: '请将手机贴近设备标签',
items[index] = items[index].copyWith( cancelText: '',
status: '已查', confirmText: '我知道了',
checkTime: barrierDismissible: false,
'${now.year}-${_two(now.month)}-${_two(now.day)} ${_two(now.hour)}:${_two(now.minute)}', );
); if (confirmed) {
}); LoadingDialogHelper.show(message: '等待设备靠近设备NFC标签');
NfcService.instance.startScanOnceWithCallback(
onResult: (uid, parsedText, rawMsg) async {
_getNFCForUid(uid, parsedText);
},
onError: (err) {
// ToastUtil.showError(context, '$err');
// await ApiService.reportCheck(items[index].code, ...); LoadingDialogHelper.hide();
},
timeout: Duration(seconds: 12),
);
}
} }
String _two(int v) => v.toString().padLeft(2, '0'); Future<void> _getNFCForUid(String uid, String parsedText) async {
if (uid.isEmpty || parsedText.isEmpty) {
ToastUtil.showError(context, 'NFC设备标签数据为空');
LoadingDialogHelper.hide();
return;
}
Map result = {};
// mapToCompactJson({'PIPELINE_AREA_ID':pd['PIPELINE_AREA_ID'],'EQUIPMENT_PIPELINE_ID':pd['EQUIPMENT_PIPELINE_ID']}),
for (Map item in items) {
if (parsedText.isNotEmpty && item['NFC_CODE'] == uid) {
try{
Map parsedData = jsonDecode(parsedText);
if (parsedData['PIPELINE_AREA_ID'] == item['PIPELINE_AREA_ID'] &&
parsedData['EQUIPMENT_PIPELINE_ID'] == item['EQUIPMENT_PIPELINE_ID']) {
result = item;
}
LoadingDialogHelper.hide();
}catch(e){
LoadingDialogHelper.hide();
ToastUtil.showError(context, 'NFC设备数据错误');
}
}
}
if (result.isEmpty) {
LoadingDialogHelper.hide();
ToastUtil.showError(context, 'NFC设备不在当前任务中');
return;
}
LoadingDialogHelper.hide();
Map data = {...result, ...widget.info, "NFC_CODE": uid, 'MANUAL_CONFIRMATION': '0',
};
await pushPage(
HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false,),
context,
);
_getTaskDetail(refresh: true);
}
@override @override
void dispose() { void dispose() {
@ -150,7 +198,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
super.dispose(); super.dispose();
} }
Widget _pendingTopCard(Map<String, dynamic> item) { Widget _pendingTopCard(Map item) {
return SizedBox( return SizedBox(
height: 180, height: 180,
child: Stack( child: Stack(
@ -232,45 +280,43 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
} }
/// ///
Widget _buildInfoGrid(Map<String, dynamic> item) { Widget _buildInfoGrid(Map item) {
return Expanded( return Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ // const SizedBox(height: 8),
// const SizedBox(height: 8), ItemListWidget.singleLineTitleText(
ItemListWidget.singleLineTitleText( label: '负责部门',
label: '负责部门', isEditable: false,
isEditable: false, text: item['DEPARTMENT_NAME'] ?? '',
text: item['DEPARTMENT_NAME'] ?? '', ),
), ItemListWidget.singleLineTitleText(
ItemListWidget.singleLineTitleText( label: '负责人',
label: '负责人', isEditable: false,
isEditable: false, text: item['USER_NAME'] ?? '',
text: item['USER_NAME'] ?? '', ),
), ItemListWidget.singleLineTitleText(
ItemListWidget.singleLineTitleText( label: '涉及管道区域:',
label: '涉及管道区域:', isEditable: false,
isEditable: false, text: item['PIPELINE_AREAS_NAMES'] ?? '',
text: item['PIPELINE_AREAS_NAMES'] ?? '', ),
), ItemListWidget.singleLineTitleText(
ItemListWidget.singleLineTitleText( label: '巡检周期',
label: '巡检周期', isEditable: false,
isEditable: false, text: item['PATROL_PERIOD_NAME'] ?? '',
text: item['PATROL_PERIOD_NAME'] ?? '', ),
), ItemListWidget.singleLineTitleText(
ItemListWidget.singleLineTitleText( label: '任务时间',
label: '任务时间', isEditable: false,
isEditable: false, text: item['OPERATTIME'] ?? '',
text: item['OPERATTIME'] ?? '', ),
), ],
],
),
); );
} }
Widget _buildListItem(BuildContext context, int idx) { Widget _buildListItem(BuildContext context, int idx) {
final item = items[idx]; final item = items[idx];
final bool unchecked = item.status == '未查'; final bool unchecked = item['INSPECTED_FLAG'] == '0';
return Container( return Container(
height: 100, height: 100,
@ -294,7 +340,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
Positioned( Positioned(
top: 2, top: 2,
child: Text( child: Text(
item.status, unchecked ? '未查' : '已查',
style: const TextStyle(color: Colors.white, fontSize: 14), style: const TextStyle(color: Colors.white, fontSize: 14),
), ),
), ),
@ -309,51 +355,97 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
// //
Expanded( Expanded(
child: Column( child: GestureDetector(
crossAxisAlignment: CrossAxisAlignment.start, onTap: (){
children: [ if (!unchecked) {
Text( Map data = {...item, ...widget.info, "NFC_CODE": item['PIPELINE_AREA_ID'], 'MANUAL_CONFIRMATION': '0',
item.location, };
style: const TextStyle( pushPage(
fontSize: 16, HomeNfcCheckDangerPage(info: data, facebookImages: [], isNfcError: false,),
fontWeight: FontWeight.bold, context,
);
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['PIPELINE_AREA_NAME'] ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1, //
overflow: TextOverflow.ellipsis, //
), ),
maxLines: 1, // const SizedBox(height: 4),
overflow: TextOverflow.ellipsis, // Text('NFC编码${item['NFC_CODE'] ?? ''}'),
), const SizedBox(height: 6),
const SizedBox(height: 4), unchecked
Text('NFC编码${item.code}'), ? Row(
const SizedBox(height: 6), spacing: 10,
unchecked mainAxisAlignment: MainAxisAlignment.spaceBetween,
? InkWell( children: [
onTap: () => _startCheckItem(item, idx), Expanded(
child: Container( child: InkWell(
height: 35, onTap: () => _startCheckItem(item, idx),
alignment: Alignment.center, child: Container(
padding: const EdgeInsets.symmetric(vertical: 1), height: 35,
decoration: BoxDecoration( // width: 120,
gradient: const LinearGradient( alignment: Alignment.center,
colors: [Color(0xFFFFA726), Color(0xFFFF7043)], padding: const EdgeInsets.symmetric(vertical: 1),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color(0xFFFFA726),
Color(0xFFFF7043),
],
),
borderRadius: BorderRadius.circular(5),
),
child: const Text(
'NFC检查',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
), ),
borderRadius: BorderRadius.circular(5),
),
child: const Text(
'开始检查',
style: TextStyle(color: Colors.white, fontSize: 14),
), ),
), ),
) CustomButton(
: Text( onPressed: () {
'检查时间:${item.checkTime ?? ''}', pushPage(
style: const TextStyle(fontSize: 14), NfcQuestionFecebook(
textAlign: TextAlign.center, info: item,
), taskInfo: widget.info,
], ),
), context,
);
},
text: '手动检查',
height: 35,
textStyle: TextStyle(
color: Colors.white,
fontSize: 15,
),
backgroundColor: Colors.blue,
),
],
)
: Text(
'检查时间:${item['PATROL_TIME'] ?? ''}',
style: const TextStyle(fontSize: 14),
textAlign: TextAlign.center,
),
],
),
)
), ),
// //
const SizedBox(width: 8), const SizedBox(width: 8),
if (!unchecked)
const Icon(Icons.chevron_right, color: Colors.grey), const Icon(Icons.chevron_right, color: Colors.grey),
], ],
), ),
@ -371,6 +463,7 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
children: [ children: [
_pendingTopCard(widget.info), _pendingTopCard(widget.info),
const SizedBox(height: 60), const SizedBox(height: 60),
Expanded( Expanded(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -384,61 +477,133 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
), ),
], ],
), ),
child: RefreshIndicator( // +
onRefresh: () => _getTaskDetail(refresh: true), child: Column(
child: children: [
firstLoad && isLoading //
? const Center( Padding(
child: Padding( padding: const EdgeInsets.symmetric(
padding: EdgeInsets.all(16.0), horizontal: 12,
child: CircularProgressIndicator(), vertical: 10,
),
child: Builder(
builder: (context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 8,
), ),
)
: items.isEmpty child: Row(
? ListView( children: [
controller: _scrollController, Text(
physics: const AlwaysScrollableScrollPhysics(), '已查点位 $_total / $_totalResult ',
children: [ style: const TextStyle(
const SizedBox(height: 80), fontSize: 14,
Center( fontWeight: FontWeight.w600,
child: Text(isLoading ? '加载中...' : '暂无数据'), ),
), ),
], const Spacer(),
) if (isLoading)
: ListView.builder( const SizedBox(
controller: _scrollController, width: 18,
physics: const AlwaysScrollableScrollPhysics(), height: 18,
itemCount: items.length + 1, // / child: CircularProgressIndicator(
itemBuilder: (ctx, idx) { strokeWidth: 2,
if (idx < items.length) {
return _buildListItem(ctx, idx);
} else {
// /
if (hasMore) {
//
if (!isLoading) {
//
// _getTaskDetail(); // scroll listener
}
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
), ),
child: Center( )
child: CircularProgressIndicator(), else
GestureDetector(
onTap:
() => _getTaskDetail(refresh: true),
child: Row(
children: const [
Icon(
Icons.refresh,
size: 16,
color: Colors.grey,
),
SizedBox(width: 4),
Text(
'刷新',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
), ),
); ),
} else { ],
return const Padding( ),
padding: EdgeInsets.symmetric( );
vertical: 12, },
),
),
//
Expanded(
child: RefreshIndicator(
onRefresh: () => _getTaskDetail(refresh: true),
child:
firstLoad && isLoading
? const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
),
)
: items.isEmpty
? ListView(
//
controller: _scrollController,
physics:
const AlwaysScrollableScrollPhysics(),
children: [
const SizedBox(height: 40),
Center(
child: Text(
isLoading ? '加载中...' : '暂无数据',
),
), ),
child: Center(child: Text('没有更多了')), ],
); )
} : ListView.builder(
} controller: _scrollController,
}, physics:
), const AlwaysScrollableScrollPhysics(),
itemCount:
items.length + 1, // /
itemBuilder: (ctx, idx) {
if (idx < items.length) {
return _buildListItem(ctx, idx);
} else {
// /
if (hasMore) {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
),
child: Center(
child:
CircularProgressIndicator(),
),
);
} else {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 12,
),
child: Center(child: Text('没有更多了')),
);
}
}
},
),
),
),
],
), ),
), ),
), ),
@ -449,96 +614,3 @@ class _HomeNfcDetailPageState extends State<HomeNfcDetailPage> {
); );
} }
} }
///
class ProgressItem {
final String status; //
final String location; //
final String code; //
final String? checkTime; //
ProgressItem({
required this.status,
required this.location,
required this.code,
this.checkTime,
});
ProgressItem copyWith({
String? status,
String? location,
String? code,
String? checkTime,
}) {
return ProgressItem(
status: status ?? this.status,
location: location ?? this.location,
code: code ?? this.code,
checkTime: checkTime ?? this.checkTime,
);
}
///
factory ProgressItem.fromJson(Map<String, dynamic> json) {
String status =
(json['status'] ??
json['STATUS'] ??
(json['checked'] == true ? '已查' : null) ??
(json['is_checked'] == 1 ? '已查' : null) ??
json['state'] ??
json['check_status'])
?.toString() ??
'';
if (status.isEmpty) {
// API 0/1
final s = json['status'] ?? json['STATUS'] ?? json['check_status'];
if (s is num) {
status = (s == 0) ? '未查' : '已查';
} else if (s is String && (s == '0' || s == '1')) {
status = (s == '0') ? '未查' : '已查';
}
}
if (status.isEmpty) status = '未查';
final location =
(json['location'] ??
json['LOCATION'] ??
json['point_name'] ??
json['address'] ??
json['point'] ??
'')
.toString();
final code =
(json['code'] ??
json['CODE'] ??
json['nfcCode'] ??
json['nfc_code'] ??
json['NFC_CODE'] ??
json['id'] ??
'')
.toString();
final checkTime =
(json['checkTime'] ??
json['CHECK_TIME'] ??
json['checked_at'] ??
json['check_time'] ??
json['inspect_time'] ??
null)
?.toString();
return ProgressItem(
status: status,
location: location,
code: code,
checkTime: checkTime,
);
}
Map<String, dynamic> toJson() => {
'status': status,
'location': location,
'code': code,
'checkTime': checkTime,
};
}

View File

@ -0,0 +1,583 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:intl/intl.dart';
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
import 'package:qhd_prevention/customWidget/bottom_picker.dart';
import 'package:qhd_prevention/customWidget/bottom_picker_two.dart';
import 'package:qhd_prevention/customWidget/custom_button.dart';
import 'package:qhd_prevention/customWidget/date_picker_dialog.dart';
import 'package:qhd_prevention/customWidget/department_person_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_two.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/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';
import '../../../customWidget/photo_picker_row.dart';
import '../../../http/ApiService.dart';
class NfcCheckDangerDetail extends StatefulWidget {
const NfcCheckDangerDetail({super.key, required this.info});
final Map<String, dynamic> info;
@override
State<NfcCheckDangerDetail> createState() => _NfcCheckDangerDetailState();
}
class _NfcCheckDangerDetailState extends State<NfcCheckDangerDetail> {
late Map<String, dynamic> pd = {};
//
late List<dynamic> _hazardLeveLlist = [];
late List<dynamic> _hiddenTypeList = [];
late bool _isDanger = false; //true 1 false 2
//
Map<String, dynamic> _personCache = {};
//
late List<String> imgList = [];
late List<String> _videos = [];
//
late List<String> zgImgList = [];
@override
void initState() {
// TODO: implement initState
super.initState();
pd = widget.info;
if (pd.isNotEmpty) {
imgList = pd['imgList'] ?? [];
_videos = pd['videoList'] ?? [];
zgImgList = pd['gzImageList'] ?? [];
}
_getHazardLevel();
}
Future<void> _getHazardLevel() async {
final resultLevel = await ApiService.getHiddenLevelsListTwo();
List<dynamic> levelList = resultLevel['list'] as List;
String parentId = '';
for (var item in levelList) {
if ((item['BIANMA'] as String).contains(
SessionService.instance.loginUser?["PROVINCE"] ?? '',
)) {
parentId = item['DICTIONARIES_ID'];
break;
}
}
final result = await ApiService.getHiddenTypeList(parentId);
final nodes = result['zTreeNodes'] as String;
SessionService.instance.departmentHiddenTypeJsonStr = nodes;
setState(() {
_hiddenTypeList = json.decode(nodes) as List;
});
try {
final result = await ApiService.getHazardLevel();
if (result['result'] == 'success') {
final List<dynamic> newList = result['list'] ?? [];
setState(() {
_hazardLeveLlist = newList;
});
}
} catch (e) {
print('Error fetching data: $e');
}
}
Future<void> _pickHazardType() async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(_) => DepartmentPickerHiddenType(
onSelected: (result) {
try {
final Map m = Map.from(result);
final ids = List<String>.from(m['id'] ?? []);
final names = List<String>.from(m['name'] ?? []);
setState(() {
pd['HIDDENTYPE'] = ids;
pd['HIDDENTYPE_NAME'] = names.join('/');
});
FocusHelper.clearFocus(context);
} catch (_) {}
},
),
);
}
Future<void> chooseDangerLevel() async {
FocusScope.of(context).unfocus();
WidgetsBinding.instance.addPostFrameCallback((_) async {
Map _hazardLeve = {};
String choice = await BottomPickerTwo.show<String>(
context,
items: _hazardLeveLlist,
itemBuilder: (item) => Text(item["NAME"], textAlign: TextAlign.center),
initialIndex: 0,
);
if (choice != null) {
for (int i = 0; i < _hazardLeveLlist.length; i++) {
if (choice == _hazardLeveLlist[i]["NAME"]) {
_hazardLeve = _hazardLeveLlist[i];
}
}
setState(() {
pd['HIDDENLEVELNAME'] = _hazardLeve["NAME"];
pd['HIDDENLEVEL'] = _hazardLeve["BIANMA"];
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "隐患登记"),
body: Column(
children: [
//
_pageDetail(),
],
),
);
}
///
void chooseUnitHandle(String typeStr) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
barrierColor: Colors.black54,
backgroundColor: Colors.transparent,
builder:
(_) => DepartmentPicker(
onSelected: (id, name) async {
setState(() {
pd['RECTIFICATIONDEPT'] = id;
pd['RECTIFICATIONDEPTNAME'] = name;
});
FocusHelper.clearFocus(context);
_getPersonListForUnitId(typeStr);
},
),
).then((_) {});
}
Future<void> _getPersonListForUnitId(String typeStr) async {
String unitId = pd['RECTIFICATIONDEPT'] ?? '';
//
final result = await ApiService.getListTreePersonList(unitId);
setState(() {
_personCache[typeStr] = List<Map<String, dynamic>>.from(
result['userList'] as List,
);
});
FocusHelper.clearFocus(context);
}
///
void choosePersonHandle(String typeStr) async {
final personList = _personCache[typeStr];
if (!FormUtils.hasValue(_personCache, typeStr)) {
ToastUtil.showNormal(context, '请先选择单位');
return;
}
DepartmentPersonPicker.show(
context,
personsData: personList!,
onSelectedWithIndex: (userId, name, index) {
setState(() {
pd['RECTIFICATIONOR'] = userId;
pd['RECTIFICATIONORNAME'] = name;
});
FocusHelper.clearFocus(context);
},
).then((_) {});
}
Widget _buildSectionContainer({required Widget child}) {
return Container(
margin: const EdgeInsets.only(top: 10),
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: child,
);
}
Widget _pageDetail() {
return Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 20),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ItemListWidget.itemContainer(
horizontal: 5,
Column(
children: [
RepairedPhotoSection(
title: "隐患照片",
maxCount: 4,
initialMediaPaths: imgList,
mediaType: MediaType.image,
isShowAI: true,
onMediaAdded: (localPath) {
imgList.add(localPath);
},
onMediaRemoved: (localPath) {
imgList.remove(localPath);
},
onChanged: (v) {},
onAiIdentify: () {
// AI
if (imgList.isEmpty) {
ToastUtil.showNormal(context, "请先上传一张图片");
return;
}
if (imgList.length > 1) {
ToastUtil.showNormal(context, "识别暂时只能上传一张图片");
return;
}
_identifyImg(imgList[0]);
},
),
RepairedPhotoSection(
title: "隐患视频",
maxCount: 1,
mediaType: MediaType.video,
onChanged: (v) {},
onMediaRemoved: (localPath) {
_videos = [localPath];
},
onMediaAdded: (localPath) {
_videos = [];
},
onAiIdentify: () {
// AI
},
),
],
),
),
ItemListWidget.multiLineTitleTextField(
label: '隐患描述',
isEditable: true,
isRequired: false,
text: pd['HIDDENDESCR'] ?? '',
hintText: '请对隐患进行详细描述(必填项)',
onChanged: (v) {
setState(() {
pd['HIDDENDESCR'] = v;
});
},
),
const Divider(),
ItemListWidget.multiLineTitleTextField(
label: '隐患部位',
isEditable: true,
isRequired: false,
// controller: TextEditingController(text: pd['HIDDENPART'] ?? ''),
text: pd['HIDDENPART'] ?? '',
hintText: '请对隐患部位进行详细描述(必填项)',
onChanged: (v) {
setState(() {
pd['HIDDENPART'] = v;
});
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '隐患级别',
isRequired: false,
isEditable: true,
text: pd['HIDDENLEVELNAME'] ?? '',
onTap: chooseDangerLevel,
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '隐患类型',
isEditable: true,
isRequired: false,
text: pd['HIDDENTYPE_NAME'] ?? '',
onTap: _pickHazardType,
),
const Divider(),
ListItemFactory.createYesNoSection(
title: "是否立即整改",
horizontalPadding: 0,
verticalPadding: 0,
yesLabel: "",
noLabel: "",
groupValue: _isDanger,
onChanged: (val) {
setState(() {
_isDanger = val;
});
},
),
const Divider(),
if (_isDanger)
Column(
children: [
ItemListWidget.multiLineTitleTextField(
label: '整改描述',
isEditable: true,
isRequired: false,
text: pd['RECTIFYDESCR'] ?? '',
hintText: '请对隐患进行详细描述(必填项)',
onChanged: (v) {
setState(() {
pd['RECTIFYDESCR'] = v;
});
},
),
const Divider(),
RepairedPhotoSection(
horizontalPadding: 12,
title: "整改后图片",
maxCount: 4,
initialMediaPaths: zgImgList,
mediaType: MediaType.image,
isShowAI: false,
onMediaAdded: (localPath) {
zgImgList.add(localPath);
},
onMediaRemoved: (localPath) {
zgImgList.remove(localPath);
},
onChanged: (v) {},
onAiIdentify: () {},
),
],
),
if (!_isDanger)
Column(
children: [
ItemListWidget.selectableLineTitleTextRightButton(
label: '整改责任部门',
isEditable: true,
isRequired: false,
text: pd['RECTIFICATIONDEPTNAME'] ?? '',
onTap: () {
chooseUnitHandle('key');
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '整改责任人',
isEditable: true,
isRequired: false,
text: pd['RECTIFICATIONORNAME'] ?? '',
onTap: () {
choosePersonHandle('key');
},
),
const Divider(),
ItemListWidget.selectableLineTitleTextRightButton(
label: '整改期限',
isEditable: true,
isRequired: false,
text: pd['RECTIFICATIONDEADLINE'] ?? '',
onTap: () {
showDialog(
context: context,
builder:
(_) => HDatePickerDialog(
initialDate: DateTime.now(),
onCancel: () => Navigator.of(context).pop(),
onConfirm: (selected) {
Navigator.of(context).pop();
setState(() {
pd['RECTIFICATIONDEADLINE'] = DateFormat(
'yyyy-MM-dd',
).format(selected);
});
},
),
);
},
),
const Divider(),
],
),
SizedBox(height: 30),
CustomButton(
onPressed: () {
_riskListCheckAppAdd();
},
text: "确定",
backgroundColor: Colors.blue,
),
SizedBox(height: 30),
],
),
),
),
);
}
Future<void> _riskListCheckAppAdd() async {
if (imgList.isEmpty) {
ToastUtil.showNormal(context, '请上传隐患图片');
return;
}
final textRules = <Map<String, dynamic>>[
{'value': pd['HIDDENDESCR'] ?? '', 'message': '请填隐患描述'},
{'value': pd['HIDDENPART'] ?? '', 'message': '请填隐患部位'},
{'value': pd['HIDDENLEVEL'] ?? '', 'message': '请选择隐患级别'},
{'value': pd['HIDDENTYPE_NAME'] ?? '', 'message': '请选择隐患类型'},
];
for (Map rule in textRules) {
String value = rule['value'];
if (value.isEmpty) {
ToastUtil.showNormal(context, rule['message']);
return;
}
}
if (_isDanger) {
if (!FormUtils.hasValue(pd, 'RECTIFYDESCR')) {
ToastUtil.showNormal(context, '请填整改描述');
return;
}
if (zgImgList.isEmpty) {
ToastUtil.showNormal(context, '请上传整改后图片');
return;
}
} else {
if (!FormUtils.hasValue(pd, 'RECTIFICATIONDEPT')) {
ToastUtil.showNormal(context, '请选择整改部门');
return;
}
if (!FormUtils.hasValue(pd, 'RECTIFICATIONOR')) {
ToastUtil.showNormal(context, '请选择整改人');
return;
}
if (!FormUtils.hasValue(pd, 'RECTIFICATIONDEADLINE')) {
ToastUtil.showNormal(context, '请选择整改期限');
return;
}
}
List HIDDENTYPE = pd['HIDDENTYPE'] ?? [];
pd['RECTIFICATIONTYPE'] = _isDanger ? '1' : '2';
pd['CREATOR'] = SessionService.instance.loginUserId;
pd['SOURCE'] = '6';
pd['HIDDENTYPE1'] = HIDDENTYPE.length > 0 ? HIDDENTYPE[0] : "";
pd['HIDDENTYPE2'] = HIDDENTYPE.length > 1 ? HIDDENTYPE[1] : "";
pd['HIDDENTYPE3'] = HIDDENTYPE.length > 2 ? HIDDENTYPE[2] : "";
pd['imgList'] = imgList;
pd['videoList'] = _videos;
pd['gzImageList'] = zgImgList;
Navigator.pop(context, pd);
}
Future<void> _identifyImg(String imagePath) async {
try {
LoadingDialogHelper.show();
final raw = await ApiService.identifyImg(imagePath);
if (raw['result'] == 'success') {
final dynamic parsedRes = raw;
final aiHiddens = parsedRes['aiHiddens'];
String hiddenDescr = '';
String rectificationSuggestions = '';
String legalBasis = '';
if (aiHiddens is List) {
for (var item in aiHiddens) {
dynamic obj = item;
if (item is String) {
try {
obj = json.decode(item);
} catch (e) {
//
continue;
}
}
if (obj is Map) {
hiddenDescr += (obj['hiddenDescr']?.toString() ?? '') + ';';
rectificationSuggestions +=
(obj['rectificationSuggestions']?.toString() ?? '') + ';';
legalBasis += (obj['legalBasis']?.toString() ?? '') + ';';
}
}
}
// pd Map
setState(() {
pd['HIDDENDESCR'] = hiddenDescr;
pd['LEGALBASIS'] = legalBasis;
pd['RECTIFYDESCR'] = rectificationSuggestions;
});
LoadingDialogHelper.hide();
} else {
ToastUtil.showNormal(context, "识别失败");
LoadingDialogHelper.hide();
// _showMessage('反馈提交失败');
// return "";
}
} catch (e) {
// Toast
print('加载首页数据失败:$e');
// return "";
LoadingDialogHelper.hide();
}
}
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
//
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled.');
}
//
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.',
);
}
//
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
}
}

View File

@ -0,0 +1,241 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qhd_prevention/customWidget/photo_picker_row.dart';
import 'package:qhd_prevention/customWidget/toast_util.dart';
import 'package:qhd_prevention/http/ApiService.dart';
import 'package:qhd_prevention/pages/home/NFC/home_nfc_check_danger_page.dart';
import 'package:qhd_prevention/pages/my_appbar.dart';
import 'package:qhd_prevention/tools/tools.dart';
// feedback_type.dart
enum NfcFeedbackType {
readError('读取失败', 0),
nfcBld('标签损坏', 1),
nfcLose('标签丢失', 2);
final String typeName;
final int type;
const NfcFeedbackType(this.typeName, this.type);
}
class NfcQuestionFecebook extends StatefulWidget {
const NfcQuestionFecebook({super.key, required this.info,required this.taskInfo});
final Map taskInfo;
final Map info;
@override
State<NfcQuestionFecebook> createState() => _NfcQuestionFecebookState();
}
class _NfcQuestionFecebookState extends State<NfcQuestionFecebook> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _descriptionController = TextEditingController();
final FocusNode _descriptionFocus = FocusNode(); // <-
//
NfcFeedbackType? _selectedType = NfcFeedbackType.readError;
//
List<String> _images = [];
@override
void initState() {
// TODO: implement initState
super.initState();
_getFacebookDetail();
}
Future<void> _getFacebookDetail() async {
final result = await ApiService.getNfcFeedBackDetail({});
try{
if (result['result'] == 'success') {
}
}catch(e) {}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppbar(title: "NFC异常上报"),
body: Container(
color: Colors.white,
child: Form(
key: _formKey,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
const Text(
'详细问题',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
TextFormField(
controller: _descriptionController,
focusNode: _descriptionFocus,
autofocus: false,
maxLines: 5,
decoration: const InputDecoration(
hintText: '请补充详细问题...',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请补充详细问题';
}
return null;
},
),
const SizedBox(height: 24),
//
const Text(
'反馈类型',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Wrap(
spacing: 16,
children:
NfcFeedbackType.values.map((type) {
return ChoiceChip(
label: Text(type.typeName),
selected: _selectedType == type,
onSelected: (selected) {
setState(() {
if (selected) {
_selectedType = type;
}
});
},
);
}).toList(),
),
//
const SizedBox(height: 16),
RepairedPhotoSection(
horizontalPadding: 0,
title: "请提供相关问题照片",
maxCount: 4,
mediaType: MediaType.image,
isCamera: true,
onChanged: (files) {
// files
_images.clear();
for (int i = 0; i < files.length; i++) {
_images.add(files[i].path);
}
},
onAiIdentify: () {},
),
const SizedBox(height: 40),
//
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _submitFeedback,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'下一步',
style: TextStyle(fontSize: 18, color: Colors.white),
),
),
),
],
),
),
),
),
);
}
//
Future<void> _submitFeedback() async {
final text = _descriptionController.text.trim();
if (text.isEmpty) {
_showMessage('请填写问题');
return;
}
if (_images.isEmpty) {
_showMessage('请上传图片');
return;
}
Map data = {
...widget.info,
...widget.taskInfo,
'EXCEPTION_TYPE': _selectedType?.type,
'MANUAL_CONFIRMATION': '1',
// 'PHOTO_URL': imagePaths,
'DESCRIPTION': text,
};
pushPage(HomeNfcCheckDangerPage(info: data, facebookImages: _images, isNfcError: true,), context);
// String imagePaths = "";
// for (int i = 0; i < _images.length; i++) {
// String imagePath = await _reloadFeedBack(_images[i]);
//
// if (0 == i) {
// imagePaths = imagePath;
// } else {
// imagePaths = "$imagePaths,$imagePath";
// }
// }
//
// _setFeedBack(text, imagePaths);
}
@override
void dispose() {
_descriptionController.dispose();
super.dispose();
}
Future<void> _setFeedBack(String text, String imagePaths) async {
try {
Map data = {
"PATROL_TASK_ID":widget.taskInfo['PATROL_TASK_ID']?? '',
"PIPELINE_AREA_ID" :widget.taskInfo['PIPELINE_AREA_ID']?? '',
"EQUIPMENT_PIPELINE_ID" :widget.info['EQUIPMENT_PIPELINE_ID']?? '',
"PATROL_RECORD_ID" :widget.info['EQUIPMENT_PIPELINE_ID'] ?? '',
"PATROL_RECORD_DETAIL_ID" :widget.info['PATROL_RECORD_DETAIL_ID']?? '',
"NFC_CODE" :widget.info['PIPELINE_AREA_ID'],
'EXCEPTION_TYPE': _selectedType?.type,
'PHOTO_URL': imagePaths,
'DESCRIPTION': text,
};
final raw = await ApiService.nfcFeedBack(data);
if (raw['result'] == 'success') {
_showMessage('提交成功');
} else {
_showMessage('提交失败');
}
} catch (e) {
// Toast
print('加载首页数据失败:$e');
}
}
void _showMessage(String msg) {
ToastUtil.showNormal(context, msg);
}
}

View File

@ -120,8 +120,9 @@ class ItemListWidget {
Expanded( Expanded(
child: child:
isEditable isEditable
? TextField( ? TextFormField(
autofocus: false, autofocus: false,
initialValue: text,
controller: controller, controller: controller,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
maxLines: null, maxLines: null,
@ -131,9 +132,10 @@ 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,
border: InputBorder.none, border: InputBorder.none,
), ),
) )

View File

@ -24,6 +24,7 @@ import 'package:nfc_manager_ndef/nfc_manager_ndef.dart';
/// iOS Xcode NFC Android NFC /// iOS Xcode NFC Android NFC
class NfcService { class NfcService {
NfcService._internal(); NfcService._internal();
static final NfcService instance = NfcService._internal(); static final NfcService instance = NfcService._internal();
/// NFC /// NFC
@ -31,6 +32,7 @@ class NfcService {
/// 广便 UI /// 广便 UI
final StreamController<String> _logController = StreamController.broadcast(); final StreamController<String> _logController = StreamController.broadcast();
Stream<String> get logs => _logController.stream; Stream<String> get logs => _logController.stream;
/// NFC /// NFC
@ -50,7 +52,10 @@ class NfcService {
/// bytes -> "AA:BB:CC" /// bytes -> "AA:BB:CC"
String _bytesToHex(Uint8List bytes) { String _bytesToHex(Uint8List bytes) {
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(':').toUpperCase(); return bytes
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join(':')
.toUpperCase();
} }
/// Map/List (List<int> / Uint8List) /// Map/List (List<int> / Uint8List)
@ -101,12 +106,14 @@ class NfcService {
if (Platform.isAndroid) { if (Platform.isAndroid) {
try { try {
final nfcA = NfcAAndroid.from(tag); final nfcA = NfcAAndroid.from(tag);
if (nfcA != null && nfcA.tag.id != null) return _bytesToHex(Uint8List.fromList(nfcA.tag.id)); if (nfcA != null && nfcA.tag.id != null)
return _bytesToHex(Uint8List.fromList(nfcA.tag.id));
} catch (_) {} } catch (_) {}
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
try { try {
final mifare = MiFareIos.from(tag); final mifare = MiFareIos.from(tag);
if (mifare != null && mifare.identifier != null) return _bytesToHex(Uint8List.fromList(mifare.identifier)); if (mifare != null && mifare.identifier != null)
return _bytesToHex(Uint8List.fromList(mifare.identifier));
} catch (_) {} } catch (_) {}
} }
} catch (e) { } catch (e) {
@ -131,9 +138,10 @@ class NfcService {
String _formatMessageToString(dynamic msg) { String _formatMessageToString(dynamic msg) {
if (msg == null) return '<empty>'; if (msg == null) return '<empty>';
try { try {
final records = (msg is Map && msg['records'] != null) final records =
? List<dynamic>.from(msg['records']) (msg is Map && msg['records'] != null)
: (msg is dynamic ? (msg.records as List<dynamic>?) : null); ? List<dynamic>.from(msg['records'])
: (msg is dynamic ? (msg.records as List<dynamic>?) : null);
if (records == null || records.isEmpty) return '<empty>'; if (records == null || records.isEmpty) return '<empty>';
final sb = StringBuffer(); final sb = StringBuffer();
for (var i = 0; i < records.length; i++) { for (var i = 0; i < records.length; i++) {
@ -144,12 +152,16 @@ class NfcService {
Uint8List? p; Uint8List? p;
if (r is Map && r['payload'] != null) { if (r is Map && r['payload'] != null) {
final ptmp = r['payload']; final ptmp = r['payload'];
if (ptmp is Uint8List) p = ptmp; if (ptmp is Uint8List)
else if (ptmp is List<int>) p = Uint8List.fromList(ptmp); p = ptmp;
else if (ptmp is List<int>)
p = Uint8List.fromList(ptmp);
} else { } else {
final pr = (r as dynamic).payload; final pr = (r as dynamic).payload;
if (pr is Uint8List) p = pr; if (pr is Uint8List)
else if (pr is List<int>) p = Uint8List.fromList(pr); p = pr;
else if (pr is List<int>)
p = Uint8List.fromList(pr);
} }
if (p != null) { if (p != null) {
final txt = parseTextFromPayload(p); final txt = parseTextFromPayload(p);
@ -171,7 +183,8 @@ class NfcService {
/// onError(error) - /// onError(error) -
/// timeout - /// timeout -
Future<void> startScanOnceWithCallback({ Future<void> startScanOnceWithCallback({
required void Function(String uid, String parsedText, dynamic rawMessage) onResult, required void Function(String uid, String parsedText, dynamic rawMessage)
onResult,
void Function(Object error)? onError, void Function(Object error)? onError,
Duration? timeout, Duration? timeout,
}) async { }) async {
@ -215,8 +228,12 @@ class NfcService {
if ((rawMsg.records as List).isNotEmpty) { if ((rawMsg.records as List).isNotEmpty) {
final first = rawMsg.records.first; final first = rawMsg.records.first;
final payload = first.payload; final payload = first.payload;
if (payload is Uint8List) parsedText = parseTextFromPayload(payload); if (payload is Uint8List)
else if (payload is List<int>) parsedText = parseTextFromPayload(Uint8List.fromList(payload)); parsedText = parseTextFromPayload(payload);
else if (payload is List<int>)
parsedText = parseTextFromPayload(
Uint8List.fromList(payload),
);
} }
} }
} else { } else {
@ -226,14 +243,24 @@ class NfcService {
// 退 tag map cachedMessage / records // 退 tag map cachedMessage / records
try { try {
if (tag is Map) { if (tag is Map) {
rawMsg = tag['cachedMessage'] ?? tag['ndef']?['cachedMessage'] ?? tag['message']; rawMsg =
tag['cachedMessage'] ??
tag['ndef']?['cachedMessage'] ??
tag['message'];
if (rawMsg != null) { if (rawMsg != null) {
final recs = (rawMsg['records'] ?? (rawMsg as dynamic).records) as dynamic; final recs =
(rawMsg['records'] ?? (rawMsg as dynamic).records)
as dynamic;
if (recs != null && recs is List && recs.isNotEmpty) { if (recs != null && recs is List && recs.isNotEmpty) {
final r = recs.first; final r = recs.first;
final p = (r is Map) ? r['payload'] : (r as dynamic).payload; final p =
if (p is Uint8List) parsedText = parseTextFromPayload(p); (r is Map) ? r['payload'] : (r as dynamic).payload;
else if (p is List<int>) parsedText = parseTextFromPayload(Uint8List.fromList(p)); if (p is Uint8List)
parsedText = parseTextFromPayload(p);
else if (p is List<int>)
parsedText = parseTextFromPayload(
Uint8List.fromList(p),
);
} }
} }
} }
@ -242,7 +269,9 @@ class NfcService {
// //
onResult(uid, parsedText, rawMsg); onResult(uid, parsedText, rawMsg);
_logController.add('UID: $uid\nmessage: ${_formatMessageToString(rawMsg)}'); _logController.add(
'UID: $uid\nmessage: ${_formatMessageToString(rawMsg)}',
);
await stopSession(); await stopSession();
} catch (e) { } catch (e) {
@ -255,7 +284,6 @@ class NfcService {
}, },
// //
pollingOptions: {NfcPollingOption.iso14443}, pollingOptions: {NfcPollingOption.iso14443},
); );
} catch (e) { } catch (e) {
scanning.value = false; scanning.value = false;
@ -265,7 +293,9 @@ class NfcService {
} }
/// Future 'UID\n文本' /// Future 'UID\n文本'
Future<String> readOnceText({Duration timeout = const Duration(seconds: 10)}) async { Future<String> readOnceText({
Duration timeout = const Duration(seconds: 10),
}) async {
final completer = Completer<String>(); final completer = Completer<String>();
await startScanOnceWithCallback( await startScanOnceWithCallback(
onResult: (uid, parsedText, rawMsg) { onResult: (uid, parsedText, rawMsg) {
@ -301,8 +331,14 @@ class NfcService {
/// - onComplete: (ok, err) /// - onComplete: (ok, err)
/// ///
/// true onComplete /// true onComplete
Future<bool> writeText(String text, {Duration? timeout, void Function(bool ok, Object? err)? onComplete}) async { Future<bool> writeText(
String text, {
Duration? timeout,
void Function(bool ok, Object? err)? onComplete,
}) async {
debugPrint('writeText called - scanning=${scanning.value}');
final available = await isAvailable(); final available = await isAvailable();
debugPrint('writeText: isAvailable=$available');
if (!available) { if (!available) {
onComplete?.call(false, 'NFC not available'); onComplete?.call(false, 'NFC not available');
return false; return false;
@ -312,6 +348,12 @@ class NfcService {
return false; return false;
} }
try {
await NfcManager.instance.stopSession();
} catch (e) {
debugPrint('stopSession ignore: $e');
}
scanning.value = true; scanning.value = true;
Timer? timer; Timer? timer;
if (timeout != null) { if (timeout != null) {
@ -324,63 +366,85 @@ class NfcService {
bool success = false; bool success = false;
try { try {
await NfcManager.instance.startSession(onDiscovered: (dynamic tag) async { // pollingOptions
try { final polling =
final ndef = Ndef.from(tag); Platform.isIOS
if (ndef == null) { ? {NfcPollingOption.iso14443} // iOS 使
onComplete?.call(false, 'Tag 不支持 NDEF'); : {
await stopSession(); NfcPollingOption.iso14443,
return; NfcPollingOption.iso15693,
} NfcPollingOption.iso18092,
};
final payload = _buildTextPayload(text, lang: 'en'); await NfcManager.instance.startSession(
final record = NdefRecord( pollingOptions: polling,
typeNameFormat: TypeNameFormat.wellKnown, onDiscovered: (dynamic tag) async {
type: Uint8List.fromList('T'.codeUnits),
identifier: Uint8List(0),
payload: payload,
);
final message = NdefMessage(records: [record]);
await ndef.write(message: message);
// UID ( nfca.identifier id)
String? uid;
if (Platform.isAndroid) {
try {
final nfcA = NfcAAndroid.from(tag);
if (nfcA != null && nfcA.tag.id != null) {
uid = _bytesToHex(Uint8List.fromList(nfcA.tag.id));
}
} catch (_) {}
} else if (Platform.isIOS) {
try {
final mifare = MiFareIos.from(tag);
if (mifare != null && mifare.identifier != null) {
uid = _bytesToHex(Uint8List.fromList(mifare.identifier));
}
} catch (_) {}
}
success = true;
onComplete?.call(true, uid); // UID
_logController.add('NFC write success, UID=$uid');
} catch (e) {
debugPrint('NFC write error: $e');
onComplete?.call(false, e);
} finally {
await stopSession();
timer?.cancel(); timer?.cancel();
scanning.value = false; try {
} final ndef = Ndef.from(tag);
}, pollingOptions: {NfcPollingOption.iso14443}); if (ndef == null) {
onComplete?.call(false, 'Tag not NDEF');
await stopSession();
scanning.value = false;
return;
}
} catch (e) { //
scanning.value = false; if (Platform.isIOS) {
final miFare = MiFareIos.from(tag);
if (miFare == null) {
onComplete?.call(false, 'Unsupported tag type on iOS');
await stopSession();
scanning.value = false;
return;
}
}
final payload = _buildTextPayload(text, lang: 'en');
final record = NdefRecord(
typeNameFormat: TypeNameFormat.wellKnown,
type: Uint8List.fromList('T'.codeUnits),
identifier: Uint8List(0),
payload: payload,
);
final message = NdefMessage(records: [record]);
await ndef.write(message: message);
// UID ( nfca.identifier id)
String? uid;
if (Platform.isAndroid) {
try {
final nfcA = NfcAAndroid.from(tag);
if (nfcA != null && nfcA.tag.id != null) {
uid = _bytesToHex(Uint8List.fromList(nfcA.tag.id));
}
} catch (_) {}
} else if (Platform.isIOS) {
try {
final mifare = MiFareIos.from(tag);
if (mifare != null && mifare.identifier != null) {
uid = _bytesToHex(Uint8List.fromList(mifare.identifier));
}
} catch (_) {}
}
success = true;
onComplete?.call(true, uid); // UID
} catch (e, st) {
debugPrint('iOS NFC Write Error: $e');
debugPrint('Stack trace: $st');
onComplete?.call(false, e);
} finally {
await stopSession();
scanning.value = false;
}
},
);
} catch (e, st) {
debugPrint('startSession exception: $e\n$st');
timer?.cancel(); timer?.cancel();
scanning.value = false;
onComplete?.call(false, e); onComplete?.call(false, e);
} }
return success; return success;
} }

View File

@ -1,12 +1,19 @@
import 'dart:math'; import 'dart:math';
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
int getRandomWithNum(int min, int max) { int getRandomWithNum(int min, int max) {
if (max < min) {
//
final tmp = min;
min = max;
max = tmp;
}
final random = Random(); final random = Random();
return random.nextInt(max) + min; // return random.nextInt(max - min + 1) + min; // [min, max]
} }
double screenWidth(BuildContext context) { double screenWidth(BuildContext context) {
@ -332,27 +339,48 @@ void presentPage(BuildContext context, Widget page) {
MaterialPageRoute(fullscreenDialog: true, builder: (_) => page), MaterialPageRoute(fullscreenDialog: true, builder: (_) => page),
); );
} }
class LoadingDialogHelper { class LoadingDialogHelper {
// static Timer? _timer;
static void show({String? message}) {
/// 10
static void show({String? message, Duration timeout = const Duration(seconds: 10)}) {
//
_timer?.cancel();
if (message != null) { if (message != null) {
EasyLoading.show(status: message); EasyLoading.show(status: message);
} else { } else {
EasyLoading.show(); EasyLoading.show();
} }
//
_timer = Timer(timeout, () {
// dismiss访 isShow
try {
EasyLoading.dismiss();
} catch (e) {
debugPrint('EasyLoading.dismiss error: $e');
}
_timer?.cancel();
_timer = null;
});
} }
// ///
static void hide() { static void hide() {
if (EasyLoading.isShow) { //
_timer?.cancel();
_timer = null;
try {
EasyLoading.dismiss(); EasyLoading.dismiss();
} catch (e) {
debugPrint('EasyLoading.dismiss error: $e');
} }
} }
} }
/// HH:MM:SS /// HH:MM:SS
String secondsCount(dynamic seconds) { String secondsCount(dynamic seconds) {
// double // double