From 3c7e992489fb1cbc0fa19f55b8aa0f143908cb5f Mon Sep 17 00:00:00 2001 From: leon <916117771@qq.com> Date: Mon, 30 Mar 2026 16:08:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(example):=20=E5=AE=9E=E7=8E=B0=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=9A=84=E4=BA=BA=E8=84=B8=E8=AF=86=E5=88=AB=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现注册流程: 人脸检测 -> 活体检测 -> 特征提取(REGISTER) -> 存储 - 实现验证流程: 人脸检测 -> 活体检测 -> 特征提取(RECOGNIZE) -> 比对 - 使用 extractFaceFeature() 和 compareFaceFeature() API - 使用 SharedPreferences 存储人脸特征数据 - 支持注册/验证模式切换 - 显示相似度和验证结果 Co-Authored-By: Claude Opus 4.6 --- example/lib/main.dart | 534 ++++++++++++++++++++++++++---------------- example/pubspec.yaml | 91 +++++++ 2 files changed, 418 insertions(+), 207 deletions(-) create mode 100644 example/pubspec.yaml diff --git a/example/lib/main.dart b/example/lib/main.dart index acbd535..469cdf5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'dart:async'; -import 'dart:math'; +import 'dart:typed_data'; +import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:arc/arc.dart'; import 'package:camera/camera.dart'; +import 'package:shared_preferences/shared_preferences.dart'; void main() { runApp(const MyApp()); @@ -123,8 +125,8 @@ class _HomePageState extends State { final result = await _arcPlugin.init( detectMode: 0, // 0=VIDEO 模式, 1=IMAGE 模式 orient: 0, // 检测角度优先级 - maxFaceNum: 10, // 最大检测人脸数 - combinedMask: 0x9D, // 功能组合掩码(人脸检测+识别+年龄+性别+RGB活体) + maxFaceNum: 1, // 单人脸识别场景 + combinedMask: 0x9D, // 功能组合掩码 ); if (result != null) { @@ -146,8 +148,8 @@ class _HomePageState extends State { } } - /// 跳转到摄像头预览页面 - void _navigateToCameraPreview() async { + /// 跳转到人脸识别页面 + void _navigateToFaceRecognition() async { try { final cameras = await availableCameras(); if (!mounted) return; @@ -155,7 +157,7 @@ class _HomePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => CameraPreviewScreen( + builder: (context) => FaceRecognitionScreen( arcPlugin: _arcPlugin, cameras: cameras, ), @@ -253,16 +255,16 @@ class _HomePageState extends State { ), const SizedBox(height: 24), - // 摄像头预览按钮 + // 人脸识别按钮 ElevatedButton( - onPressed: _isInitialized ? _navigateToCameraPreview : null, + onPressed: _isInitialized ? _navigateToFaceRecognition : null, style: ElevatedButton.styleFrom( backgroundColor: _isInitialized ? Colors.purple : Colors.grey, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text( - '打开摄像头预览', + '开始人脸识别', style: TextStyle(fontSize: 16), ), ), @@ -284,10 +286,11 @@ class _HomePageState extends State { ), SizedBox(height: 8), Text( - '1. 请先替换代码中的 _appId、_sdkKey 和 _activeKey 为您的真实密钥\n' - '2. 点击"激活 SDK"按钮进行在线激活\n' - '3. 激活成功后点击"初始化引擎"按钮\n' - '4. 初始化成功后点击"打开摄像头预览"进行人脸检测', + '1. 点击"激活 SDK"按钮进行在线激活\n' + '2. 激活成功后点击"初始化引擎"按钮\n' + '3. 点击"开始人脸识别"进行注册/验证\n\n' + '注册流程: 人脸检测 → 活体检测 → 特征提取 → 存储\n' + '验证流程: 人脸检测 → 活体检测 → 特征提取 → 比对', style: TextStyle(fontSize: 12), ), ], @@ -301,42 +304,46 @@ class _HomePageState extends State { } } -/// 摄像头预览页面,用于实时人脸检测和 RGB 活体检测 -class CameraPreviewScreen extends StatefulWidget { +/// 人脸识别页面 - 实现完整的注册和验证流程 +class FaceRecognitionScreen extends StatefulWidget { final Arc arcPlugin; final List cameras; - const CameraPreviewScreen({ + const FaceRecognitionScreen({ super.key, required this.arcPlugin, required this.cameras, }); @override - State createState() => _CameraPreviewScreenState(); + State createState() => _FaceRecognitionScreenState(); } -class _CameraPreviewScreenState extends State { +class _FaceRecognitionScreenState extends State { CameraController? _cameraController; - - bool _isDetecting = false; - int _faceCount = 0; - String _lastDetectionTime = ''; + bool _isProcessing = false; String _statusMessage = '正在初始化摄像头...'; - // RGB 活体检测相关状态 + // 检测结果 + int _faceCount = 0; int _rgbLivenessResult = -1; // -1=未检测, 0=非真人, 1=真人 - String _rgbLivenessStatus = '未检测'; - // 人脸ID管理 - /// 存储人脸特征到ID的映射(使用人脸矩形区域的hashCode作为特征标识) - final Map _faceIdMap = {}; - /// 当前帧检测到的人脸ID列表 - List _currentFaceIds = []; - /// 当前帧检测到的人脸特征标识列表(用于注册时关联) - List _currentFaceKeys = []; - /// 随机数生成器 - final Random _random = Random(); + // 注册的人脸特征数据存储 key + static const String _storedFeatureKey = 'stored_face_feature'; + + // 当前帧的人脸信息 + Map? _currentFaceInfo; + Uint8List? _currentNv21Data; + int _currentWidth = 0; + int _currentHeight = 0; + + // 模式: 0=注册, 1=验证 + int _mode = 0; + String _modeLabel = '注册模式'; + + // 验证结果 + double _similarity = 0.0; + bool _verified = false; @override void initState() { @@ -347,35 +354,22 @@ class _CameraPreviewScreenState extends State { /// 初始化摄像头 Future _initCamera() async { if (widget.cameras.isEmpty) { - debugPrint('没有可用的摄像头'); setState(() { _statusMessage = '没有可用的摄像头'; }); return; } - // 打印所有摄像头信息 - debugPrint('========== 摄像头列表 =========='); - for (int i = 0; i < widget.cameras.length; i++) { - final camera = widget.cameras[i]; - debugPrint('摄像头 $i:'); - debugPrint(' - name: ${camera.name}'); - debugPrint(' - lensDirection: ${camera.lensDirection}'); - debugPrint(' - sensorOrientation: ${camera.sensorOrientation}'); - } - debugPrint('================================'); - - // 选择后置摄像头作为默认摄像头 + // 选择前置摄像头 CameraDescription? selectedCamera; for (var camera in widget.cameras) { - if (camera.lensDirection == CameraLensDirection.back) { + if (camera.lensDirection == CameraLensDirection.front) { selectedCamera = camera; break; } } selectedCamera ??= widget.cameras.first; - debugPrint('选择摄像头: ${selectedCamera.name}'); _cameraController = CameraController( selectedCamera, ResolutionPreset.medium, @@ -385,19 +379,13 @@ class _CameraPreviewScreenState extends State { try { await _cameraController!.initialize(); - debugPrint('摄像头初始化成功'); - - final size = _cameraController!.value.previewSize; - debugPrint('预览尺寸: ${size?.width}x${size?.height}'); - if (mounted) { setState(() { - _statusMessage = '摄像头: ${selectedCamera?.name ?? "未知"}'; + _statusMessage = '摄像头就绪'; }); _startImageStream(); } } catch (e) { - debugPrint('摄像头初始化失败: $e'); if (mounted) { setState(() { _statusMessage = '摄像头初始化失败: $e'; @@ -409,91 +397,66 @@ class _CameraPreviewScreenState extends State { /// 启动图像流 void _startImageStream() { _cameraController?.startImageStream(_onImageAvailable); - debugPrint('已启动图像流'); } - /// 图像帧回调,进行人脸检测 + /// 图像帧回调 - 人脸检测 void _onImageAvailable(CameraImage image) async { - if (_isDetecting) return; - _isDetecting = true; + if (_isProcessing) return; + _isProcessing = true; try { final int width = image.width; final int height = image.height; - - // 将图像数据转换为连续的 NV21 格式 final nv21Data = _extractNV21(image, width, height); - // 人脸检测(同时进行 RGB 活体检测) + // 保存当前帧数据用于后续特征提取 + _currentNv21Data = nv21Data; + _currentWidth = width; + _currentHeight = height; + + // 人脸检测 + RGB 活体检测 final detectResult = await widget.arcPlugin.detectFaces( data: nv21Data, width: width, height: height, - format: 2050, // NV21 格式 + format: 2050, ); if (detectResult != null) { final success = detectResult['success'] == true; final faceList = detectResult['faceList'] as List? ?? []; + final rgbLiveness = detectResult['rgbLiveness'] as int? ?? -1; if (success && faceList.isNotEmpty) { - final now = DateTime.now(); - final timeStr = '${now.hour.toString().padLeft(2, '0')}:' - '${now.minute.toString().padLeft(2, '0')}:' - '${now.second.toString().padLeft(2, '0')}.' - '${now.millisecond.toString().padLeft(3, '0')}'; - - debugPrint('========== 人脸检测 [$timeStr] =========='); - debugPrint('检测到 ${faceList.length} 张人脸'); - - for (int i = 0; i < faceList.length; i++) { - final face = faceList[i] as Map; - debugPrint('人脸 ${i + 1}: (${face['rectLeft']}, ${face['rectTop']}, ${face['rectRight']}, ${face['rectBottom']})'); - } - - // 获取 RGB 活体检测结果 - final rgbLiveness = detectResult['rgbLiveness'] as int? ?? -1; - final rgbIsAlive = rgbLiveness == 1; + // 保存第一张人脸信息 + _currentFaceInfo = Map.from(faceList[0] as Map); if (mounted) { setState(() { _faceCount = faceList.length; - _lastDetectionTime = timeStr; _rgbLivenessResult = rgbLiveness; - _rgbLivenessStatus = rgbIsAlive ? '真人' : (rgbLiveness == 0 ? '非真人' : '未知'); - // 更新当前人脸特征标识和ID列表 - _currentFaceKeys = faceList.map((face) { - return _getFaceKey(face as Map); - }).toList(); - _currentFaceIds = _currentFaceKeys.map((key) { - return _faceIdMap[key] ?? '未注册'; - }).toList(); + _statusMessage = _rgbLivenessResult == 1 ? '检测到真人' : '检测中...'; }); } - debugPrint('RGB 活体: liveness=$rgbLiveness, isAlive=$rgbIsAlive'); - debugPrint('========================================'); } else { - // 未检测到人脸时重置状态 + _currentFaceInfo = null; if (mounted) { setState(() { _faceCount = 0; _rgbLivenessResult = -1; - _rgbLivenessStatus = '未检测'; - _currentFaceIds = []; - _currentFaceKeys = []; + _statusMessage = '未检测到人脸'; }); } } } - } catch (e, stackTrace) { + } catch (e) { debugPrint('人脸检测异常: $e'); - debugPrint('堆栈: $stackTrace'); } finally { - _isDetecting = false; + _isProcessing = false; } } - /// 提取 NV21 数据(处理 stride 对齐) + /// 提取 NV21 数据 Uint8List _extractNV21(CameraImage image, int width, int height) { final ySize = width * height; final nv21Size = ySize * 3 ~/ 2; @@ -550,27 +513,182 @@ class _CameraPreviewScreenState extends State { return nv21; } - /// 生成随机人脸ID - /// 生成格式: F + 8位随机数字 - String _generateRandomFaceId() { - final id = _random.nextInt(100000000).toString().padLeft(8, '0'); - return 'F$id'; + /// 注册人脸 + Future _registerFace() async { + if (_currentFaceInfo == null || _currentNv21Data == null) { + _showMessage('未检测到人脸'); + return; + } + + if (_rgbLivenessResult != 1) { + _showMessage('请确保是真人'); + return; + } + + setState(() { + _statusMessage = '正在提取特征...'; + }); + + try { + // 提取特征 (REGISTER 模式) + final result = await widget.arcPlugin.extractFaceFeature( + data: _currentNv21Data!, + width: _currentWidth, + height: _currentHeight, + rectLeft: _currentFaceInfo!['rectLeft'] as int, + rectTop: _currentFaceInfo!['rectTop'] as int, + rectRight: _currentFaceInfo!['rectRight'] as int, + rectBottom: _currentFaceInfo!['rectBottom'] as int, + faceOrientation: _currentFaceInfo!['orient'] as int? ?? 0, + faceId: _currentFaceInfo!['faceId'] as int? ?? -1, + extractType: 0, // REGISTER 模式 + mask: 0, + ); + + if (result != null && result['success'] == true) { + final featureData = result['featureData'] as Uint8List; + + // 存储特征数据到本地 + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_storedFeatureKey, _bytesToBase64(featureData)); + + setState(() { + _statusMessage = '注册成功!特征已保存'; + }); + _showMessage('人脸注册成功!'); + } else { + final errorMsg = result?['message'] ?? '未知错误'; + setState(() { + _statusMessage = '特征提取失败: $errorMsg'; + }); + } + } catch (e) { + setState(() { + _statusMessage = '注册异常: $e'; + }); + } } - /// 根据人脸矩形区域生成特征标识 - String _getFaceKey(Map face) { - // 使用人脸矩形区域的位置和大小作为特征标识 - final rectLeft = face['rectLeft'] as int? ?? 0; - final rectTop = face['rectTop'] as int? ?? 0; - final rectRight = face['rectRight'] as int? ?? 0; - final rectBottom = face['rectBottom'] as int? ?? 0; - // 使用位置和大小计算一个简单的特征值(允许一定容差) - final centerX = (rectLeft + rectRight) ~/ 2; - final centerY = (rectTop + rectBottom) ~/ 2; - // 按照区块划分(每50像素为一个区块) - final blockX = centerX ~/ 50; - final blockY = centerY ~/ 50; - return '$blockX-$blockY'; + /// 验证人脸 + Future _verifyFace() async { + if (_currentFaceInfo == null || _currentNv21Data == null) { + _showMessage('未检测到人脸'); + return; + } + + if (_rgbLivenessResult != 1) { + _showMessage('请确保是真人'); + return; + } + + // 读取存储的特征 + final prefs = await SharedPreferences.getInstance(); + final storedFeatureBase64 = prefs.getString(_storedFeatureKey); + + if (storedFeatureBase64 == null) { + _showMessage('请先注册人脸'); + return; + } + + setState(() { + _statusMessage = '正在提取特征...'; + }); + + try { + // 提取当前帧特征 (RECOGNIZE 模式) + final result = await widget.arcPlugin.extractFaceFeature( + data: _currentNv21Data!, + width: _currentWidth, + height: _currentHeight, + rectLeft: _currentFaceInfo!['rectLeft'] as int, + rectTop: _currentFaceInfo!['rectTop'] as int, + rectRight: _currentFaceInfo!['rectRight'] as int, + rectBottom: _currentFaceInfo!['rectBottom'] as int, + faceOrientation: _currentFaceInfo!['orient'] as int? ?? 0, + faceId: _currentFaceInfo!['faceId'] as int? ?? -1, + extractType: 1, // RECOGNIZE 模式 + mask: 0, + ); + + if (result != null && result['success'] == true) { + final currentFeature = result['featureData'] as Uint8List; + final storedFeature = _base64ToBytes(storedFeatureBase64); + + // 比对特征 + final compareResult = await widget.arcPlugin.compareFaceFeature( + featureData1: currentFeature, + featureData2: storedFeature, + compareModel: 0, // LIFE_PHOTO 模式 + ); + + if (compareResult != null && compareResult['success'] == true) { + final similarity = compareResult['similarity'] as double? ?? 0.0; + final threshold = 0.8; // 推荐阈值 + final isMatch = similarity >= threshold; + + setState(() { + _similarity = similarity; + _verified = isMatch; + _statusMessage = isMatch + ? '验证通过!相似度: ${(similarity * 100).toStringAsFixed(1)}%' + : '验证失败!相似度: ${(similarity * 100).toStringAsFixed(1)}%'; + }); + } else { + setState(() { + _statusMessage = '比对失败'; + }); + } + } else { + setState(() { + _statusMessage = '特征提取失败'; + }); + } + } catch (e) { + setState(() { + _statusMessage = '验证异常: $e'; + }); + } + } + + /// 切换模式 + void _toggleMode() { + setState(() { + _mode = _mode == 0 ? 1 : 0; + _modeLabel = _mode == 0 ? '注册模式' : '验证模式'; + _similarity = 0.0; + _verified = false; + }); + } + + /// 清除注册数据 + Future _clearRegistration() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_storedFeatureKey); + setState(() { + _similarity = 0.0; + _verified = false; + _statusMessage = '已清除注册数据'; + }); + _showMessage('注册数据已清除'); + } + + /// 字节数组转 Base64 + String _bytesToBase64(Uint8List bytes) { + return base64Encode(bytes); + } + + /// Base64 转字节数组 + Uint8List _base64ToBytes(String base64) { + return base64Decode(base64); + } + + /// 显示消息 + void _showMessage(String message) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); + } } @override @@ -584,9 +702,21 @@ class _CameraPreviewScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('人脸检测预览'), + title: Text(_modeLabel), backgroundColor: Colors.purple, foregroundColor: Colors.white, + actions: [ + IconButton( + icon: const Icon(Icons.swap_horiz), + onPressed: _toggleMode, + tooltip: '切换模式', + ), + IconButton( + icon: const Icon(Icons.delete_outline), + onPressed: _clearRegistration, + tooltip: '清除注册数据', + ), + ], ), body: Column( children: [ @@ -595,10 +725,7 @@ class _CameraPreviewScreenState extends State { child: _cameraController != null && _cameraController!.value.isInitialized ? Center( - child: RotatedBox( - quarterTurns: -1, - child: CameraPreview(_cameraController!), - ), + child: CameraPreview(_cameraController!), ) : Center( child: Column( @@ -612,7 +739,7 @@ class _CameraPreviewScreenState extends State { ), ), - // 检测信息显示 + // 检测信息和操作面板 Container( width: double.infinity, padding: const EdgeInsets.all(16), @@ -620,26 +747,34 @@ class _CameraPreviewScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // 状态信息 Text( - '检测到人脸数: $_faceCount', + _statusMessage, style: const TextStyle( color: Colors.white, - fontSize: 18, + fontSize: 16, fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 4), - Text( - '最后检测时间: $_lastDetectionTime', - style: const TextStyle( - color: Colors.white70, - fontSize: 14, - ), - ), const SizedBox(height: 8), - // RGB 活体检测状态 + + // 人脸和活体状态 Row( children: [ + Icon( + _faceCount > 0 ? Icons.face : Icons.face_retouching_off, + color: _faceCount > 0 ? Colors.green : Colors.grey, + size: 20, + ), + const SizedBox(width: 8), + Text( + '人脸: ${_faceCount > 0 ? "已检测" : "未检测"}', + style: TextStyle( + color: _faceCount > 0 ? Colors.green : Colors.grey, + fontSize: 14, + ), + ), + const SizedBox(width: 16), Icon( _rgbLivenessResult == 1 ? Icons.check_circle @@ -655,98 +790,83 @@ class _CameraPreviewScreenState extends State { ), const SizedBox(width: 8), Text( - 'RGB活体: $_rgbLivenessStatus', + '活体: ${_rgbLivenessResult == 1 ? "真人" : _rgbLivenessResult == 0 ? "非真人" : "未知"}', style: TextStyle( color: _rgbLivenessResult == 1 ? Colors.green : _rgbLivenessResult == 0 ? Colors.red - : Colors.white70, + : Colors.orange, fontSize: 14, ), ), ], ), - // 人脸ID显示 - if (_currentFaceIds.isNotEmpty) ...[ + + // 验证结果显示 + if (_mode == 1 && _similarity > 0) ...[ const SizedBox(height: 8), const Divider(color: Colors.white24), const SizedBox(height: 8), - Text( - '人脸ID:', - style: const TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Wrap( - spacing: 8, - runSpacing: 4, - children: _currentFaceIds.asMap().entries.map((entry) { - final index = entry.key; - final id = entry.value; - final isRegistered = id != '未注册'; - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, + Row( + children: [ + Icon( + _verified ? Icons.verified_user : Icons.warning, + color: _verified ? Colors.green : Colors.red, + size: 24, + ), + const SizedBox(width: 8), + Text( + '相似度: ${(_similarity * 100).toStringAsFixed(1)}%', + style: TextStyle( + color: _verified ? Colors.green : Colors.red, + fontSize: 18, + fontWeight: FontWeight.bold, ), - decoration: BoxDecoration( - color: isRegistered ? Colors.green.withValues(alpha: 0.3) : Colors.orange.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: isRegistered ? Colors.green : Colors.orange, - ), + ), + const SizedBox(width: 8), + Text( + _verified ? '验证通过' : '非本人', + style: TextStyle( + color: _verified ? Colors.green : Colors.red, + fontSize: 14, ), - child: Text( - '人脸${index + 1}: $id', - style: TextStyle( - color: isRegistered ? Colors.greenAccent : Colors.orangeAccent, - fontSize: 12, - ), - ), - ); - }).toList(), + ), + ], ), ], - // 注册按钮 - if (_faceCount > 0) ...[ - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () { - // 为未注册的人脸生成随机ID,并存入映射表 - setState(() { - for (int i = 0; i < _faceCount; i++) { - if (_currentFaceIds[i] == '未注册') { - final newId = _generateRandomFaceId(); - final faceKey = _currentFaceKeys[i]; - // 将特征标识和ID存入映射表 - _faceIdMap[faceKey] = newId; - _currentFaceIds[i] = newId; - } - } - }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('人脸ID已生成!'), - backgroundColor: Colors.green, - ), - ); - }, - icon: const Icon(Icons.person_add), - label: const Text('注册人脸(生成随机ID)'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), + + const SizedBox(height: 12), + + // 操作按钮 + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: _mode == 0 ? _registerFace : _verifyFace, + icon: Icon(_mode == 0 ? Icons.person_add : Icons.verified_user), + label: Text(_mode == 0 ? '注册人脸' : '验证身份'), + style: ElevatedButton.styleFrom( + backgroundColor: _mode == 0 ? Colors.blue : Colors.green, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + ), ), ), + ], + ), + + // 提示 + const SizedBox(height: 8), + Text( + _mode == 0 + ? '提示: 注册时会提取人脸特征并保存到本地' + : '提示: 验证时会与已注册的特征进行比对,阈值 0.8', + style: const TextStyle( + color: Colors.white54, + fontSize: 11, ), - ], + ), ], ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..f44c13e --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,91 @@ +name: arc_example +description: "Demonstrates how to use the arc plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ^3.9.0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + arc: + # When depending on this package from a real application you should use: + # arc: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + + # 摄像头插件 + camera: ^0.11.1 + + # 本地存储 + shared_preferences: ^2.2.2 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package