v1.2.1 待发布

This commit is contained in:
2025-12-22 16:15:32 +08:00
parent 21a528d6d1
commit 9ba152b3c3
5 changed files with 312 additions and 272 deletions

View File

@@ -20,7 +20,9 @@ PODS:
- FlutterMacOS - FlutterMacOS
- image_picker_ios (0.0.1): - image_picker_ios (0.0.1):
- Flutter - Flutter
- MTBBarcodeScanner (5.0.11) - mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
- OrderedSet (6.0.3) - OrderedSet (6.0.3)
- package_info_plus (0.4.5): - package_info_plus (0.4.5):
- Flutter - Flutter
@@ -29,9 +31,6 @@ PODS:
- FlutterMacOS - FlutterMacOS
- permission_handler_apple (9.3.0): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- qr_code_scanner_plus (0.2.6):
- Flutter
- MTBBarcodeScanner
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@@ -47,16 +46,15 @@ DEPENDENCIES:
- flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`) - flutter_pdfview (from `.symlinks/plugins/flutter_pdfview/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- qr_code_scanner_plus (from `.symlinks/plugins/qr_code_scanner_plus/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`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- MTBBarcodeScanner
- OrderedSet - OrderedSet
EXTERNAL SOURCES: EXTERNAL SOURCES:
@@ -76,14 +74,14 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/geolocator_apple/darwin" :path: ".symlinks/plugins/geolocator_apple/darwin"
image_picker_ios: image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios" :path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
:path: ".symlinks/plugins/mobile_scanner/darwin"
package_info_plus: package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation: path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
qr_code_scanner_plus:
:path: ".symlinks/plugins/qr_code_scanner_plus/ios"
shared_preferences_foundation: shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios: url_launcher_ios:
@@ -98,12 +96,11 @@ SPEC CHECKSUMS:
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
qr_code_scanner_plus: 7e087021bc69873140e0754750eb87d867bed755
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b

View File

@@ -1,17 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:image/image.dart' as img;
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:ln_jq_app/common/model/base_model.dart'; import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/common/model/vehicle_info.dart'; import 'package:ln_jq_app/common/model/vehicle_info.dart';
import 'package:ln_jq_app/storage_service.dart'; import 'package:ln_jq_app/storage_service.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:zxing_lib/common.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:zxing_lib/qrcode.dart';
import 'package:zxing_lib/zxing.dart';
class QrCodeController extends GetxController class QrCodeController extends GetxController
with BaseControllerMixin, GetSingleTickerProviderStateMixin { with BaseControllerMixin, GetSingleTickerProviderStateMixin {
@@ -22,11 +18,11 @@ class QrCodeController extends GetxController
late final AnimationController animationController; late final AnimationController animationController;
late final Animation<double> scanAnimation; late final Animation<double> scanAnimation;
// --- QR Scanning --- // --- 使用 MobileScanner 的控制器 ---
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); final MobileScannerController scannerController = MobileScannerController();
QRViewController? qrViewController;
final Rx<Barcode?> result = Rx<Barcode?>(null);
final RxBool isFlashOn = false.obs; final RxBool isFlashOn = false.obs;
final RxBool isProcessingResult = false.obs;
@override @override
void onInit() { void onInit() {
@@ -37,122 +33,101 @@ class QrCodeController extends GetxController
duration: const Duration(milliseconds: 2500), duration: const Duration(milliseconds: 2500),
vsync: this, vsync: this,
); );
scanAnimation = Tween<double>(begin: 0, end: 1).animate(animationController); scanAnimation =
Tween<double>(begin: 0, end: 1).animate(animationController);
animationController.repeat(reverse: false); animationController.repeat(reverse: false);
} }
/// 当 QRView 创建时调用 /// MobileScanner 的 onDetect 回调方法
void onQRViewCreated(QRViewController controller) { void onDetect(BarcodeCapture capture) {
this.qrViewController = controller; if (isProcessingResult.value) return;
// 监听扫描到的数据
controller.scannedDataStream.listen((scanData) {
if (scanData.code != null && result.value?.code != scanData.code) {
result.value = scanData;
qrViewController?.pauseCamera();
final Barcode? barcode = capture.barcodes.firstOrNull;
if (barcode?.rawValue != null && barcode!.rawValue!.isNotEmpty) {
isProcessingResult.value = true;
scannerController.stop();
animationController.stop(); animationController.stop();
print("相机识别到的内容: ${barcode.rawValue!}");
renderResult(scanData.code!); renderResult(barcode.rawValue!);
} }
});
} }
/// 恢复扫描状态
void resumeScanner() { void resumeScanner() {
result.value = null; isProcessingResult.value = false;
qrViewController?.resumeCamera(); try {
scannerController.start();
} catch (e) {
print("无法重启相机: $e");
}
animationController.repeat(reverse: false); animationController.repeat(reverse: false);
} }
/// 从相册选择图片并扫描二维码 /// 从相册选择图片并扫描二维码
void scanFromGallery() async { void scanFromGallery() async {
try { try {
final XFile? imageFile = await ImagePicker().pickImage(source: ImageSource.gallery); final XFile? imageFile =
if (imageFile == null) return; // 用户取消了选择 await ImagePicker().pickImage(source: ImageSource.gallery);
if (imageFile == null) {
return;
}
qrViewController?.pauseCamera(); scannerController.stop();
animationController.stop(); animationController.stop();
showLoading("正在识别...");
String? scanResult; final BarcodeCapture? capture =
try { await scannerController.analyzeImage(imageFile.path);
final image = img.decodeImage(await File(imageFile.path).readAsBytes());
if (image != null) {
//扫描图片
final pixels = Int32List.fromList(
image.map((pixel) {
return (pixel.a.toInt() << 24) |
(pixel.r.toInt() << 16) |
(pixel.g.toInt() << 8) |
pixel.b.toInt();
}).toList(),
);
final source = RGBLuminanceSource(image.width, image.height, pixels); dismissLoading();
final bitmap = BinaryBitmap(HybridBinarizer(source)); final Barcode? firstBarcode = capture?.barcodes.firstOrNull;
final reader = QRCodeReader();
final result = reader.decode(bitmap);
scanResult = result.text;
}
} on NotFoundException {
scanResult = null;
} catch (e) {
//异常
scanResult = null;
}
if (scanResult != null) { if (firstBarcode?.rawValue != null &&
firstBarcode!.rawValue!.isNotEmpty) {
final String scanResult = firstBarcode.rawValue!;
print("相册识别到的内容: $scanResult");
renderResult(scanResult); renderResult(scanResult);
} else { } else {
showErrorToast('未识别到二维码'); showErrorToast('未识别到二维码');
resumeScanner(); resumeScanner();
} }
} catch (e) { } catch (e, stackTrace) {
showErrorToast('从相册选择失败'); dismissLoading();
showErrorToast('从相册选择失败,请稍后重试');
print("scanFromGallery Error: $e\n$stackTrace");
resumeScanner(); resumeScanner();
} }
} }
/// 切换闪光灯 /// 切换闪光灯
void toggleFlash() async { void toggleFlash() async {
await qrViewController?.toggleFlash(); try {
isFlashOn.value = (await qrViewController?.getFlashStatus()) ?? false; await scannerController.toggleTorch();
final currentTorchState = scannerController.value.torchState;
isFlashOn.value = currentTorchState == TorchState.on;
} catch (e) {
print("切换闪光灯失败: $e");
showErrorToast("无法打开闪光灯");
}
} }
/// 翻转相机 /// 翻转相机
void flipCamera() async { void flipCamera() async {
await qrViewController?.flipCamera(); await scannerController.switchCamera();
} }
/// 请求相机权限
void requestPermission() async { void requestPermission() async {
if (Platform.isIOS) {
var status = await Permission.camera.request(); var status = await Permission.camera.request();
if (status.isGranted) { if (!status.isGranted) {
} else if (status.isPermanentlyDenied) { showErrorToast('请授予相机权限以使用扫描功能');
openAppSettings(); Get.back();
} else {
showErrorToast('需要相机权限才能扫描二维码');
}
return;
}
final bool results = await requestCameraPermission();
if (!results) {
showErrorToast('相机权限未被授予,请到权限管理中打开');
} }
} }
void requestPhotoPermission() async { void requestPhotoPermission() async {
if (Platform.isAndroid) {
final bool results = await requestPhotosPermission();
if (!results) {
showErrorToast('相册权限未被授予,请到权限管理中打开');
} else {
scanFromGallery();
}
}
if (Platform.isIOS) {
var status = await Permission.photos.request(); var status = await Permission.photos.request();
print("权限状态: $status"); // 在控制台看这个输出
if (status.isGranted) { if (status.isGranted) {
scanFromGallery(); scanFromGallery();
} else if (status.isPermanentlyDenied) { } else if (status.isPermanentlyDenied) {
@@ -161,16 +136,11 @@ class QrCodeController extends GetxController
showErrorToast('需要相册权限才能从相册中选择图片'); showErrorToast('需要相册权限才能从相册中选择图片');
} }
} }
}
//扫码结果处理 //如果绑定接口返回的data为null 需要手动编辑车牌 /// 处理扫描结果
void renderResult(String resultStr, {plateNumber}) async { void renderResult(String resultStr, {plateNumber}) async {
showLoading("正在获取车辆信息..."); showLoading("正在获取车辆信息...");
try { try {
/*var responseData = await HttpService.to.get(
"appointment/truck/base-info?vin=$resultStr",
);*/
final Map<String, dynamic> requestData = { final Map<String, dynamic> requestData = {
"code": resultStr, "code": resultStr,
"phone": StorageService.to.phone, "phone": StorageService.to.phone,
@@ -184,6 +154,7 @@ class QrCodeController extends GetxController
); );
if (responseData == null) { if (responseData == null) {
dismissLoading();
showToast('无法获取车辆信息,请检查网络或稍后重试'); showToast('无法获取车辆信息,请检查网络或稍后重试');
resumeScanner(); resumeScanner();
return; return;
@@ -192,62 +163,76 @@ class QrCodeController extends GetxController
if (result.code != 0) { if (result.code != 0) {
showToast(result.error); showToast(result.error);
dismissLoading();
resumeScanner(); // 绑定失败也要恢复扫描
return; return;
} }
if (result.data == null) { if (result.data == null) {
dismissLoading();
showBindDialog(resultStr); showBindDialog(resultStr);
return; return;
} }
final vehicle = VehicleInfo.fromJson(result.data as Map<String, dynamic>); final vehicle = VehicleInfo.fromJson(result.data as Map<String, dynamic>);
//保存使用
await StorageService.to.saveVehicleInfo(vehicle); await StorageService.to.saveVehicleInfo(vehicle);
dismissLoading();
Get.back(result: true); Get.back(result: true);
} on DioException catch (e) {
} on DioException catch (_) {
showErrorToast("网络请求失败,请稍后重试"); showErrorToast("网络请求失败,请稍后重试");
resumeScanner(); resumeScanner();
} catch (e, stackTrace) { } catch (e, _) {
showErrorToast("处理失败,请稍后重"); showErrorToast("处理失败,请稍后重");
resumeScanner(); // 未知异常,恢复扫描 resumeScanner();
} finally { } finally {
if (Get.isDialogOpen ?? false) {
dismissLoading(); dismissLoading();
} }
} }
}
/// 显示绑定确认对话框
void showBindDialog(String resultStr) { void showBindDialog(String resultStr) {
final TextEditingController plateNumberController = TextEditingController(); final TextEditingController plateNumberController = TextEditingController();
DialogX.to.showNoticeDialog( // 使用 showConfirmDialog它有 onCancel 回调
icon: DialogIcon.info, DialogX.to.showConfirmDialog(
title: '请输入车牌号', title: '请输入车牌号',
barrierDismissible: false, barrierDismissible: false,
content: TextField( content: TextField(
controller: plateNumberController, // 绑定 controller controller: plateNumberController,
autofocus: false, // 弹窗出现时自动获取焦点,方便用户直接输入 autofocus: false,
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: '请输入完整的车牌号', hintText: '请输入完整的车牌号',
border: OutlineInputBorder(), border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12.0), contentPadding: EdgeInsets.symmetric(horizontal: 12.0),
), ),
), ),
confirmText: '确认', confirmText: '确认绑定',
cancelText: '取消', // showConfirmDialog 有 cancelText
onConfirm: () { onConfirm: () {
final String plateNumber = plateNumberController.text.trim(); final String plateNumber = plateNumberController.text.trim();
if (plateNumber.isEmpty) { if (plateNumber.isEmpty) {
resumeScanner();
showToast("请输入车牌号"); showToast("请输入车牌号");
return; // 返回 false 可以阻止弹窗关闭,让用户继续输入
return false;
} }
renderResult(resultStr, plateNumber: plateNumber); renderResult(resultStr, plateNumber: plateNumber);
//关闭弹窗
return true;
},
onCancel: () {
// 如果用户点击取消,恢复扫描
resumeScanner();
}, },
); );
} }
@override @override
void onClose() { void onClose() {
qrViewController?.dispose(); scannerController.dispose();
animationController.dispose(); animationController.dispose();
super.onClose(); super.onClose();
} }
} }

View File

@@ -1,19 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:ln_jq_app/common/styles/theme.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:mobile_scanner/mobile_scanner.dart';
import 'controller.dart'; import 'controller.dart';
class QrCodePage extends GetView<QrCodeController> { class QrCodePage extends GetView<QrCodeController> {
const QrCodePage({Key? key}) : super(key: key); const QrCodePage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GetBuilder<QrCodeController>( Get.put(QrCodeController());
init: QrCodeController(),
id: 'qrcode',
builder: (_) {
return Scaffold( return Scaffold(
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: AppBar( appBar: AppBar(
@@ -28,85 +25,89 @@ class QrCodePage extends GetView<QrCodeController> {
actions: [ actions: [
TextButton( TextButton(
onPressed: controller.requestPhotoPermission, onPressed: controller.requestPhotoPermission,
child: const Text( child: const Text('相册', style: TextStyle(color: Colors.white, fontSize: 16)),
'相册',
style: TextStyle(color: Colors.white, fontSize: 16),
),
), ),
], ],
), ),
body: Stack( body: Stack(
children: <Widget>[ alignment: Alignment.center,
_buildQrView(context), children: [
Positioned( // 1. 使用 MobileScanner 作为扫描视图
bottom: 80.h, MobileScanner(
left: 0, controller: controller.scannerController,
right: 0, onDetect: controller.onDetect,
child: _buildControlButtons(), // 您可以自定义扫描框的样式
scanWindow: Rect.fromCenter(
center: Offset(
MediaQuery.of(context).size.width / 2,
MediaQuery.of(context).size.height / 2 - 50,
), ),
width: 250,
height: 250,
),
),
// 扫描动画和覆盖层
_buildScannerOverlay(context),
// 底部的功能按钮
Positioned(bottom: 80, left: 0, right: 0, child: _buildActionButtons()),
], ],
), ),
); );
},
);
} }
/// 构建二维码扫描视图(带动画 /// 构建扫描区域的覆盖层和动画
Widget _buildQrView(BuildContext context) { Widget _buildScannerOverlay(BuildContext context) {
// 定义扫描区域的大小 // 模拟扫描框的位置和大小
var scanArea = (MediaQuery.of(context).size.width < 400 || const double scanAreaSize = 250.0;
MediaQuery.of(context).size.height < 400)
? 250.0
: 300.0;
return Stack( return Stack(
children: [
// 半透明的覆盖层
ColorFiltered(
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.5), BlendMode.srcOut),
child: Stack(
children: [
Container(decoration: const BoxDecoration(color: Colors.transparent)),
Align(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[ child: Container(
// 底层是相机视图和半透明遮罩 margin: const EdgeInsets.only(bottom: 100), // 微调位置
QRView( width: scanAreaSize,
key: controller.qrKey, height: scanAreaSize,
onQRViewCreated: controller.onQRViewCreated, decoration: BoxDecoration(
overlay: QrScannerOverlayShape( color: Colors.black,
borderColor: Colors.blueAccent, borderRadius: BorderRadius.circular(12),
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
), ),
), ),
// 上层是扫描动画 ),
AnimatedBuilder( ],
),
),
// 扫描动画
Align(
alignment: Alignment.center,
child: Container(
margin: const EdgeInsets.only(bottom: 100),
width: scanAreaSize,
height: scanAreaSize,
child: AnimatedBuilder(
animation: controller.scanAnimation, animation: controller.scanAnimation,
builder: (context, child) { builder: (context, child) {
return Positioned( return CustomPaint(
// 计算扫描框的顶部位置,以便动画从顶部开始 painter: ScannerAnimationPainter(
top: (MediaQuery.of(context).size.height - scanArea) / 2, controller.scanAnimation.value,
child: Transform.translate( AppTheme.themeColor,
offset: Offset(0, controller.scanAnimation.value * scanArea),
child: Container(
width: scanArea,
height: 2, // 扫描线的高度
decoration: BoxDecoration(
color: Colors.blueAccent,
boxShadow: [
BoxShadow(
color: Colors.blueAccent.withOpacity(0.7),
blurRadius: 8,
spreadRadius: 2,
),
],
),
),
), ),
); );
}, },
), ),
),
),
], ],
); );
} }
/// 构建底部的控制按钮 /// 构建底部的功能按钮(闪光灯、相册)
Widget _buildControlButtons() { Widget _buildActionButtons() {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -123,11 +124,16 @@ class QrCodePage extends GetView<QrCodeController> {
_buildIconButton( _buildIconButton(
onPressed: controller.toggleFlash, onPressed: controller.toggleFlash,
//闪光灯状态的变化 //闪光灯状态的变化
child: Obx(() => Icon( child: Obx(
() => IconButton(
icon: Icon(
controller.isFlashOn.value ? Icons.flash_on : Icons.flash_off, controller.isFlashOn.value ? Icons.flash_on : Icons.flash_off,
color: Colors.white, color: Colors.white,
size: 28, size: 28,
)), ),
onPressed: controller.toggleFlash,
),
),
), ),
// 翻转相机按钮 // 翻转相机按钮
_buildIconButton( _buildIconButton(
@@ -144,6 +150,7 @@ class QrCodePage extends GetView<QrCodeController> {
); );
} }
Widget _buildIconButton({required VoidCallback onPressed, required Widget child}) { Widget _buildIconButton({required VoidCallback onPressed, required Widget child}) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -158,3 +165,71 @@ class QrCodePage extends GetView<QrCodeController> {
); );
} }
} }
/// 扫描动画的绘制器
class ScannerAnimationPainter extends CustomPainter {
final double value;
final Color borderColor;
ScannerAnimationPainter(this.value, this.borderColor);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = borderColor
..strokeWidth = 3
..style = PaintingStyle.stroke;
final cornerLength = 20.0;
// 绘制四个角的边框
// Top-left
canvas.drawPath(
Path()
..moveTo(0, cornerLength)
..lineTo(0, 0)
..lineTo(cornerLength, 0),
paint,
);
// Top-right
canvas.drawPath(
Path()
..moveTo(size.width - cornerLength, 0)
..lineTo(size.width, 0)
..lineTo(size.width, cornerLength),
paint,
);
// Bottom-left
canvas.drawPath(
Path()
..moveTo(0, size.height - cornerLength)
..lineTo(0, size.height)
..lineTo(cornerLength, size.height),
paint,
);
// Bottom-right
canvas.drawPath(
Path()
..moveTo(size.width - cornerLength, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width, size.height - cornerLength),
paint,
);
// 绘制扫描线
final linePaint = Paint()
..color = borderColor.withOpacity(0.8)
..strokeWidth = 2
..shader = LinearGradient(
colors: [borderColor.withOpacity(0), borderColor, borderColor.withOpacity(0)],
stops: const [0.0, 0.5, 1.0],
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
final y = size.height * value;
canvas.drawLine(Offset(0, y), Offset(size.width, y), linePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}

View File

@@ -65,14 +65,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
charset:
dependency: transitive
description:
name: charset
sha256: "27802032a581e01ac565904ece8c8962564b1070690794f0072f6865958ce8b9"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -733,6 +725,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.1.4"
modal_bottom_sheet: modal_bottom_sheet:
dependency: transitive dependency: transitive
description: description:
@@ -925,14 +925,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.0.3" version: "6.0.3"
qr_code_scanner_plus:
dependency: "direct main"
description:
name: qr_code_scanner_plus
sha256: b764e5004251c58d9dee0c295e6006e05bd8d249e78ac3383abdb5afe0a996cd
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.14"
rational: rational:
dependency: transitive dependency: transitive
description: description:
@@ -1234,14 +1226,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
zxing_lib:
dependency: "direct main"
description:
name: zxing_lib
sha256: f9170470b6bc947d21a6783486f88ef48aad66fc1380c8acd02b118418ec0ce0
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.4"
sdks: sdks:
dart: ">=3.9.0 <4.0.0" dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0" flutter: ">=3.35.0"

View File

@@ -43,10 +43,9 @@ dependencies:
flutter_native_splash: ^2.4.7 flutter_native_splash: ^2.4.7
dropdown_button2: ^2.3.8 dropdown_button2: ^2.3.8
qr_code_scanner_plus: ^2.0.14
image_picker: ^1.2.1 # 用于从相册选择图片 image_picker: ^1.2.1 # 用于从相册选择图片
image: ^4.5.4 image: ^4.5.4
zxing_lib: ^1.1.4 mobile_scanner: ^7.1.4
flutter_pdfview: 1.4.3 #显示pdf flutter_pdfview: 1.4.3 #显示pdf
photo_view: ^0.15.0 #操作图片 photo_view: ^0.15.0 #操作图片
flutter_inappwebview: ^6.1.5 # WebView插件 flutter_inappwebview: ^6.1.5 # WebView插件