From d9631fbb80d2d845ddbb6797ea8ad2ee311a42ad Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 2 Mar 2026 22:57:35 +0800 Subject: [PATCH] add first version --- .claude/settings.local.json | 14 + aisee_flutter/Android设备准备.md | 103 +++++ aisee_flutter/README.md | 107 ++++- .../android/app/src/main/AndroidManifest.xml | 9 + aisee_flutter/install.bat | 13 + aisee_flutter/lib/main.dart | 155 ++++--- aisee_flutter/lib/screens/camera_screen.dart | 290 +++++++++++++ aisee_flutter/lib/services/api_service.dart | 79 ++++ .../lib/services/camera_service.dart | 158 ++++++++ aisee_flutter/lib/utils/app_config.dart | 13 + aisee_flutter/pubspec.lock | 383 +++++++++++++++++- aisee_flutter/pubspec.yaml | 18 + aisee_flutter/run.bat | 21 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + aisee_flutter/启动指南.md | 253 ++++++++++++ aisee_flutter/手机连接问题.md | 104 +++++ 17 files changed, 1631 insertions(+), 93 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 aisee_flutter/Android设备准备.md create mode 100644 aisee_flutter/install.bat create mode 100644 aisee_flutter/lib/screens/camera_screen.dart create mode 100644 aisee_flutter/lib/services/api_service.dart create mode 100644 aisee_flutter/lib/services/camera_service.dart create mode 100644 aisee_flutter/lib/utils/app_config.dart create mode 100644 aisee_flutter/run.bat create mode 100644 aisee_flutter/启动指南.md create mode 100644 aisee_flutter/手机连接问题.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ca96909 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(cd:*)", + "Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/src/flutter/bin/flutter pub get 2>&1)", + "Bash(cmd.exe:*)", + "Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/Users/xdedmi/flutter/bin/flutter pub get 2>&1)", + "Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/Users/xdedmi/flutter/bin/flutter devices 2>&1)", + "Bash(cd /c/Users/xdedmi/Desktop/aisee/aisee_flutter && /c/Users/xdedmi/flutter/bin/flutter emulators 2>&1)", + "Bash(adb devices:*)", + "Bash(/c/Users/xdedmi/flutter/bin/flutter doctor:*)" + ] + } +} diff --git a/aisee_flutter/Android设备准备.md b/aisee_flutter/Android设备准备.md new file mode 100644 index 0000000..13aa673 --- /dev/null +++ b/aisee_flutter/Android设备准备.md @@ -0,0 +1,103 @@ +# 快速启动 Android 模拟器 + +## 当前状态 + +✅ Flutter 已安装:`C:\Users\xdedmi\flutter` +✅ 项目依赖已安装 +❌ 没有 Android 模拟器 + +## 创建 Android 模拟器 + +### 方法 1:使用 Android Studio(推荐) + +1. **打开 Android Studio** + +2. **打开 Device Manager** + - 点击右上角的设备图标 + - 或 Tools -> Device Manager + +3. **创建新设备** + - 点击 "Create Device" + - 选择设备:Pixel 6 Pro + - 选择系统镜像:API 34 (Android 14) + - 点击 "Finish" + +4. **启动模拟器** + - 点击播放按钮启动模拟器 + - 等待 2-3 分钟启动完成 + +### 方法 2:使用命令行 + +```bash +# 1. 查看可用的系统镜像 +C:\Users\xdedmi\flutter\bin\flutter doctor + +# 2. 如果 Android Studio 已安装,创建模拟器 +# 打开 Android Studio -> Device Manager -> Create Device +``` + +## 启动项目(Android) + +模拟器启动后: + +```bash +cd C:\Users\xdedmi\Desktop\aisee\aisee_flutter + +# 检查设备 +C:\Users\xdedmi\flutter\bin\flutter devices + +# 运行项目 +C:\Users\xdedmi\flutter\bin\flutter run +``` + +## 使用真机(更快) + +如果有 Android 手机: + +1. **开启开发者选项** + - 设置 -> 关于手机 + - 连续点击"版本号" 7 次 + +2. **开启 USB 调试** + - 设置 -> 开发者选项 + - 开启 "USB 调试" + +3. **连接手机** + - USB 连接手机到电脑 + - 手机上允许 USB 调试授权 + +4. **运行项目** + ```bash + C:\Users\xdedmi\flutter\bin\flutter devices + C:\Users\xdedmi\flutter\bin\flutter run + ``` + +## 当前运行状态 + +项目正在 Windows 桌面模式编译运行,你可以先看到界面效果。 + +**注意**:相机功能需要 Android 设备才能使用。 + +## 快捷脚本 + +我已经创建了启动脚本,但需要更新 Flutter 路径。 + +编辑 `install.bat` 和 `run.bat`,在开头添加: + +```batch +@echo off +set PATH=C:\Users\xdedmi\flutter\bin;%PATH% +``` + +然后就可以双击运行了。 + +## 下一步 + +1. ⏳ 等待 Windows 版本编译完成(首次约 5-10 分钟) +2. 📱 创建 Android 模拟器或连接真机 +3. 🚀 在 Android 设备上运行项目 +4. 📷 测试相机功能 + +--- + +需要帮助创建模拟器吗? diff --git a/aisee_flutter/README.md b/aisee_flutter/README.md index c9dd7ec..50cfef4 100644 --- a/aisee_flutter/README.md +++ b/aisee_flutter/README.md @@ -1,12 +1,109 @@ -# aisee_flutter +# AISee Flutter 实时相机 -A new Flutter project. +实时相机图像捕获和传输演示项目。 -## Getting Started +## 功能特性 -This project is a starting point for a Flutter application. +- ✅ 实时相机预览 +- ✅ 图像自动压缩(640x480,质量 85%) +- ✅ 定时捕获(每 500ms 一帧) +- ✅ 异步上传到后端 +- ✅ 单张拍照并分析 +- ✅ 前后摄像头切换 +- ✅ 实时状态显示 -A few resources to get you started if this is your first Flutter project: +## 快速开始 + +### 1. 安装依赖 + +```bash +flutter pub get +``` + +### 2. 配置后端地址 + +编辑 `lib/utils/app_config.dart`: + +```dart +// Android 模拟器 +static const String apiBaseUrl = 'http://10.0.2.2:8000'; + +// 真机(改为你的电脑 IP) +static const String apiBaseUrl = 'http://192.168.1.100:8000'; +``` + +### 3. 运行项目 + +```bash +flutter run +``` + +## 项目结构 + +``` +lib/ +├── main.dart # 入口和首页 +├── screens/ +│ └── camera_screen.dart # 相机页面 +├── services/ +│ ├── camera_service.dart # 相机服务 +│ └── api_service.dart # API 服务 +└── utils/ + └── app_config.dart # 配置 +``` + +## 使用说明 + +1. **打开相机**:点击首页"打开相机"按钮 +2. **拍照分析**:拍摄单张照片并上传分析 +3. **开始传输**:每 500ms 自动捕获并上传 +4. **停止传输**:停止实时捕获 +5. **切换摄像头**:前后摄像头切换 + +## 配置参数 + +在 `lib/utils/app_config.dart` 中调整: + +```dart +static const int imageMaxWidth = 640; // 图像宽度 +static const int imageMaxHeight = 480; // 图像高度 +static const int imageQuality = 85; // JPEG 质量 +static const Duration captureInterval = Duration(milliseconds: 500); // 捕获间隔 +``` + +## 后端 API + +需要实现以下端点: + +``` +POST /api/v1/images/upload # 上传图像 +POST /api/v1/analysis/analyze # 分析图像 +``` + +详细文档见项目根目录的技术方案文档。 + +## 依赖 + +- camera: ^0.10.5+9 +- image: ^4.1.7 +- dio: ^5.4.1 +- permission_handler: ^11.3.0 +- provider: ^6.1.1 + +## 常见问题 + +**Q: 相机权限被拒绝?** +A: 在手机设置中授予相机权限并重启应用。 + +**Q: 网络连接失败?** +A: 检查后端服务是否启动,确认 IP 地址配置正确。 + +**Q: 上传速度慢?** +A: 降低图像分辨率或增加捕获间隔。 + +--- + +更多信息请查看项目文档。 - [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) - [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) diff --git a/aisee_flutter/android/app/src/main/AndroidManifest.xml b/aisee_flutter/android/app/src/main/AndroidManifest.xml index 69163ad..3a995b4 100644 --- a/aisee_flutter/android/app/src/main/AndroidManifest.xml +++ b/aisee_flutter/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,13 @@ + + + + + + + + + createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. + title: const Text('AISee'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), ), body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, + const Icon( + Icons.visibility, + size: 100, + color: Colors.blue, + ), + const SizedBox(height: 24), + const Text( + 'AISee 视觉辅助', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + const Text( + '实时相机图像传输演示', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 48), + ElevatedButton.icon( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CameraScreen(), + ), + ); + }, + icon: const Icon(Icons.camera_alt, size: 28), + label: const Text( + '打开相机', + style: TextStyle(fontSize: 18), + ), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + ), + ), + const SizedBox(height: 24), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: Text( + '功能说明:\n' + '• 拍照分析:拍摄单张照片并上传分析\n' + '• 开始传输:每 500ms 自动捕获并上传\n' + '• 切换:切换前后摄像头', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 14, + color: Colors.black87, + height: 1.5, + ), + ), ), ], ), ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), ); } } diff --git a/aisee_flutter/lib/screens/camera_screen.dart b/aisee_flutter/lib/screens/camera_screen.dart new file mode 100644 index 0000000..5f932c6 --- /dev/null +++ b/aisee_flutter/lib/screens/camera_screen.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; +import 'package:camera/camera.dart'; +import 'package:provider/provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import '../services/camera_service.dart'; +import '../services/api_service.dart'; + +class CameraScreen extends StatefulWidget { + const CameraScreen({super.key}); + + @override + State createState() => _CameraScreenState(); +} + +class _CameraScreenState extends State { + late CameraService _cameraService; + late ApiService _apiService; + bool _isProcessing = false; + String _statusMessage = '准备就绪'; + int _frameCount = 0; + int _uploadCount = 0; + + @override + void initState() { + super.initState(); + _cameraService = CameraService(); + _apiService = ApiService(); + _initializeCamera(); + } + + Future _initializeCamera() async { + // 请求相机权限 + final status = await Permission.camera.request(); + if (!status.isGranted) { + setState(() { + _statusMessage = '相机权限被拒绝'; + }); + return; + } + + // 初始化相机 + await _cameraService.initialize(); + if (mounted) { + setState(() { + _statusMessage = '相机已就绪'; + }); + } + } + + /// 开始实时传输 + void _startStreaming() { + if (_cameraService.isStreaming) return; + + setState(() { + _statusMessage = '实时传输中...'; + _frameCount = 0; + _uploadCount = 0; + }); + + _cameraService.startStreaming((imageBytes) async { + setState(() { + _frameCount++; + }); + + // 上传到后端(异步,不阻塞捕获) + _uploadToBackend(imageBytes); + }); + } + + /// 停止实时传输 + void _stopStreaming() { + _cameraService.stopStreaming(); + setState(() { + _statusMessage = '已停止传输'; + }); + } + + /// 上传图像到后端 + Future _uploadToBackend(imageBytes) async { + if (_isProcessing) return; // 防止并发上传 + + _isProcessing = true; + + try { + // 上传图像 + final result = await _apiService.uploadImage(imageBytes); + + setState(() { + _uploadCount++; + _statusMessage = '已上传 $_uploadCount 帧'; + }); + + print('上传成功: ${result['image_id']}'); + } catch (e) { + print('上传失败: $e'); + setState(() { + _statusMessage = '上传失败: $e'; + }); + } finally { + _isProcessing = false; + } + } + + /// 拍摄单张照片并分析 + Future _captureAndAnalyze() async { + setState(() { + _statusMessage = '正在拍照...'; + }); + + final imageBytes = await _cameraService.takePicture(); + if (imageBytes == null) { + setState(() { + _statusMessage = '拍照失败'; + }); + return; + } + + setState(() { + _statusMessage = '正在分析...'; + }); + + try { + // 上传并分析 + final result = await _apiService.uploadAndAnalyze(imageBytes); + + setState(() { + _statusMessage = '分析完成'; + }); + + // 显示结果 + _showAnalysisResult(result); + } catch (e) { + setState(() { + _statusMessage = '分析失败: $e'; + }); + } + } + + /// 显示分析结果 + void _showAnalysisResult(Map result) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('分析结果'), + content: SingleChildScrollView( + child: Text(result.toString()), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('关闭'), + ), + ], + ), + ); + } + + @override + void dispose() { + _cameraService.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('实时相机'), + backgroundColor: Colors.black, + ), + body: _cameraService.isInitialized + ? _buildCameraView() + : const Center(child: CircularProgressIndicator()), + ); + } + + Widget _buildCameraView() { + return Stack( + children: [ + // 相机预览 + Positioned.fill( + child: CameraPreview(_cameraService.controller!), + ), + + // 状态信息 + Positioned( + top: 16, + left: 16, + right: 16, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.7), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _statusMessage, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + '捕获帧数: $_frameCount | 上传帧数: $_uploadCount', + style: const TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + ], + ), + ), + ), + + // 控制按钮 + Positioned( + bottom: 32, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // 拍照并分析按钮 + _buildControlButton( + icon: Icons.camera_alt, + label: '拍照分析', + onPressed: _captureAndAnalyze, + color: Colors.blue, + ), + + // 开始/停止实时传输按钮 + _buildControlButton( + icon: _cameraService.isStreaming ? Icons.stop : Icons.play_arrow, + label: _cameraService.isStreaming ? '停止传输' : '开始传输', + onPressed: _cameraService.isStreaming ? _stopStreaming : _startStreaming, + color: _cameraService.isStreaming ? Colors.red : Colors.green, + ), + + // 切换摄像头按钮 + _buildControlButton( + icon: Icons.flip_camera_android, + label: '切换', + onPressed: () => _cameraService.switchCamera(), + color: Colors.orange, + ), + ], + ), + ), + ], + ); + } + + Widget _buildControlButton({ + required IconData icon, + required String label, + required VoidCallback onPressed, + required Color color, + }) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: onPressed, + backgroundColor: color, + child: Icon(icon, size: 32), + ), + const SizedBox(height: 8), + Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + shadows: [ + Shadow( + color: Colors.black, + blurRadius: 4, + ), + ], + ), + ), + ], + ); + } +} diff --git a/aisee_flutter/lib/services/api_service.dart b/aisee_flutter/lib/services/api_service.dart new file mode 100644 index 0000000..127c4ae --- /dev/null +++ b/aisee_flutter/lib/services/api_service.dart @@ -0,0 +1,79 @@ +import 'dart:typed_data'; +import 'package:dio/dio.dart'; +import '../utils/app_config.dart'; + +class ApiService { + late final Dio _dio; + + ApiService() { + _dio = Dio(BaseOptions( + baseUrl: AppConfig.apiBaseUrl, + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + headers: { + 'Content-Type': 'application/json', + }, + )); + + // 添加日志拦截器 + _dio.interceptors.add(LogInterceptor( + requestBody: false, // 不打印请求体(图片太大) + responseBody: true, + error: true, + )); + } + + /// 上传图像到后端 + Future> uploadImage(Uint8List imageBytes) async { + try { + final formData = FormData.fromMap({ + 'file': MultipartFile.fromBytes( + imageBytes, + filename: 'image_${DateTime.now().millisecondsSinceEpoch}.jpg', + ), + }); + + final response = await _dio.post( + '/api/v1/images/upload', + data: formData, + ); + + return response.data; + } catch (e) { + print('上传图像失败: $e'); + rethrow; + } + } + + /// 请求 AI 分析 + Future> analyzeImage(String imageUrl) async { + try { + final response = await _dio.post( + '/api/v1/analysis/analyze', + data: {'image_url': imageUrl}, + ); + + return response.data; + } catch (e) { + print('分析图像失败: $e'); + rethrow; + } + } + + /// 一步到位:上传并分析 + Future> uploadAndAnalyze(Uint8List imageBytes) async { + try { + // 1. 上传图像 + final uploadResult = await uploadImage(imageBytes); + final imageUrl = uploadResult['url']; + + // 2. 请求分析 + final analysisResult = await analyzeImage(imageUrl); + + return analysisResult; + } catch (e) { + print('上传并分析失败: $e'); + rethrow; + } + } +} diff --git a/aisee_flutter/lib/services/camera_service.dart b/aisee_flutter/lib/services/camera_service.dart new file mode 100644 index 0000000..03609fb --- /dev/null +++ b/aisee_flutter/lib/services/camera_service.dart @@ -0,0 +1,158 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'package:camera/camera.dart'; +import 'package:flutter/foundation.dart'; +import 'package:image/image.dart' as img; +import '../utils/app_config.dart'; + +class CameraService extends ChangeNotifier { + CameraController? _controller; + List _cameras = []; + bool _isInitialized = false; + bool _isStreaming = false; + Timer? _captureTimer; + + CameraController? get controller => _controller; + bool get isInitialized => _isInitialized; + bool get isStreaming => _isStreaming; + + /// 初始化相机 + Future initialize() async { + try { + _cameras = await availableCameras(); + if (_cameras.isEmpty) { + print('没有可用的相机'); + return; + } + + // 使用后置摄像头 + final camera = _cameras.firstWhere( + (camera) => camera.lensDirection == CameraLensDirection.back, + orElse: () => _cameras.first, + ); + + _controller = CameraController( + camera, + ResolutionPreset.medium, // 中等分辨率,平衡质量和性能 + enableAudio: false, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + await _controller!.initialize(); + _isInitialized = true; + notifyListeners(); + + print('相机初始化成功'); + } catch (e) { + print('相机初始化失败: $e'); + _isInitialized = false; + } + } + + /// 拍摄单张照片 + Future takePicture() async { + if (!_isInitialized || _controller == null) return null; + + try { + final XFile image = await _controller!.takePicture(); + final bytes = await image.readAsBytes(); + + // 压缩图像 + final compressedBytes = await _compressImage(bytes); + return compressedBytes; + } catch (e) { + print('拍照失败: $e'); + return null; + } + } + + /// 开始实时捕获(定时拍照) + void startStreaming(Function(Uint8List) onImageCaptured) { + if (_isStreaming) return; + + _isStreaming = true; + notifyListeners(); + + _captureTimer = Timer.periodic(AppConfig.captureInterval, (timer) async { + if (!_isStreaming) { + timer.cancel(); + return; + } + + final imageBytes = await takePicture(); + if (imageBytes != null) { + onImageCaptured(imageBytes); + } + }); + + print('开始实时捕获,间隔: ${AppConfig.captureInterval.inMilliseconds}ms'); + } + + /// 停止实时捕获 + void stopStreaming() { + _isStreaming = false; + _captureTimer?.cancel(); + _captureTimer = null; + notifyListeners(); + print('停止实时捕获'); + } + + /// 压缩图像 + Future _compressImage(Uint8List bytes) async { + return await compute(_compressImageIsolate, bytes); + } + + /// 在独立 Isolate 中压缩图像(避免阻塞 UI) + static Uint8List _compressImageIsolate(Uint8List bytes) { + // 解码图像 + img.Image? image = img.decodeImage(bytes); + if (image == null) return bytes; + + // 调整大小 + if (image.width > AppConfig.imageMaxWidth || + image.height > AppConfig.imageMaxHeight) { + image = img.copyResize( + image, + width: AppConfig.imageMaxWidth, + height: AppConfig.imageMaxHeight, + ); + } + + // 压缩为 JPEG + final compressed = img.encodeJpg(image, quality: AppConfig.imageQuality); + + print('图像压缩: ${bytes.length} bytes -> ${compressed.length} bytes'); + return Uint8List.fromList(compressed); + } + + /// 切换摄像头 + Future switchCamera() async { + if (_cameras.length < 2) return; + + final currentLens = _controller?.description.lensDirection; + final newCamera = _cameras.firstWhere( + (camera) => camera.lensDirection != currentLens, + orElse: () => _cameras.first, + ); + + await _controller?.dispose(); + + _controller = CameraController( + newCamera, + ResolutionPreset.medium, + enableAudio: false, + imageFormatGroup: ImageFormatGroup.jpeg, + ); + + await _controller!.initialize(); + notifyListeners(); + } + + /// 释放资源 + @override + void dispose() { + stopStreaming(); + _controller?.dispose(); + super.dispose(); + } +} diff --git a/aisee_flutter/lib/utils/app_config.dart b/aisee_flutter/lib/utils/app_config.dart new file mode 100644 index 0000000..44edea4 --- /dev/null +++ b/aisee_flutter/lib/utils/app_config.dart @@ -0,0 +1,13 @@ +class AppConfig { + // API 配置 + static const String apiBaseUrl = 'http://10.0.2.2:8000'; // Android 模拟器访问本机 + // 如果使用真机,改为你的电脑 IP,例如:'http://192.168.1.100:8000' + + // 图像配置 + static const int imageMaxWidth = 640; + static const int imageMaxHeight = 480; + static const int imageQuality = 85; + + // 实时传输配置 + static const Duration captureInterval = Duration(milliseconds: 500); // 每 500ms 捕获一帧 +} diff --git a/aisee_flutter/pubspec.lock b/aisee_flutter/pubspec.lock index 764bc87..08b4ba2 100644 --- a/aisee_flutter/pubspec.lock +++ b/aisee_flutter/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" async: dependency: transitive description: @@ -17,6 +25,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + camera: + dependency: "direct main" + description: + name: camera + sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8 + url: "https://pub.dev" + source: hosted + version: "0.10.6" + camera_android: + dependency: transitive + description: + name: camera_android + sha256: "27761a1e317bc072266dc1975f3a04c7fa02ff6123f18b1d89d9968951a4f002" + url: "https://pub.dev" + source: hosted + version: "0.10.10+15" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "11b4aee2f5e5e038982e152b4a342c749b414aa27857899d20f4323e94cb5f0b" + url: "https://pub.dev" + source: hosted + version: "0.9.23+2" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63" + url: "https://pub.dev" + source: hosted + version: "2.12.0" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7" + url: "https://pub.dev" + source: hosted + version: "0.3.5+3" characters: dependency: transitive description: @@ -33,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" collection: dependency: transitive description: @@ -41,6 +97,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -49,6 +121,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dio: + dependency: "direct main" + description: + name: dio + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" + source: hosted + version: "5.9.2" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" + source: hosted + version: "2.1.2" fake_async: dependency: transitive description: @@ -57,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -70,11 +174,56 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: "direct main" + description: + name: image + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.dev" + source: hosted + version: "4.8.0" leak_tracker: dependency: transitive description: @@ -107,6 +256,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,6 +288,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + url: "https://pub.dev" + source: hosted + version: "0.17.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" path: dependency: transitive description: @@ -139,6 +328,150 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" sky_engine: dependency: transitive description: flutter @@ -168,6 +501,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -192,6 +533,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.9" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -208,6 +557,38 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.11.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.38.4" diff --git a/aisee_flutter/pubspec.yaml b/aisee_flutter/pubspec.yaml index 57df666..5b64908 100644 --- a/aisee_flutter/pubspec.yaml +++ b/aisee_flutter/pubspec.yaml @@ -35,6 +35,24 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + # 相机 + camera: ^0.10.5+9 + + # 图像处理 + image: ^4.1.7 + + # 网络请求 + dio: ^5.4.1 + + # 权限管理 + permission_handler: ^11.3.0 + + # 状态管理 + provider: ^6.1.1 + + # 路径 + path_provider: ^2.1.2 + dev_dependencies: flutter_test: sdk: flutter diff --git a/aisee_flutter/run.bat b/aisee_flutter/run.bat new file mode 100644 index 0000000..1939d05 --- /dev/null +++ b/aisee_flutter/run.bat @@ -0,0 +1,21 @@ +@echo off +set PATH=C:\Users\xdedmi\flutter\bin;%PATH% +echo ======================================== +echo AISee Flutter 启动脚本 +echo ======================================== +echo. +cd /d %~dp0 + +echo 正在检查设备... +flutter devices +echo. + +echo 如果看到 Android 设备,按任意键继续运行... +echo 如果没有设备,请先启动模拟器或连接真机 +pause + +echo. +echo 正在启动应用... +flutter run + +pause diff --git a/aisee_flutter/windows/flutter/generated_plugin_registrant.cc b/aisee_flutter/windows/flutter/generated_plugin_registrant.cc index 8b6d468..48de52b 100644 --- a/aisee_flutter/windows/flutter/generated_plugin_registrant.cc +++ b/aisee_flutter/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/aisee_flutter/windows/flutter/generated_plugins.cmake b/aisee_flutter/windows/flutter/generated_plugins.cmake index b93c4c3..0e69e40 100644 --- a/aisee_flutter/windows/flutter/generated_plugins.cmake +++ b/aisee_flutter/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/aisee_flutter/启动指南.md b/aisee_flutter/启动指南.md new file mode 100644 index 0000000..e262530 --- /dev/null +++ b/aisee_flutter/启动指南.md @@ -0,0 +1,253 @@ +# 启动 AISee Flutter 项目指南 + +## 前提条件检查 + +### 1. 检查 Flutter 是否安装 + +打开命令提示符(CMD)或 PowerShell,运行: + +```bash +flutter --version +``` + +**如果显示版本信息**,说明 Flutter 已安装,跳到步骤 2。 + +**如果提示"命令不存在"**,需要先安装 Flutter,参考:`Flutter开发环境搭建.md` + +### 2. 检查设备连接 + +```bash +flutter devices +``` + +应该看到: +- Android 模拟器,或 +- 连接的 Android 真机 + +**如果没有设备**: +- 启动 Android 模拟器(Android Studio -> Device Manager) +- 或连接真机并开启 USB 调试 + +## 启动步骤 + +### 方法 1:使用脚本(推荐) + +1. **安装依赖** + - 双击 `install.bat` + - 等待依赖下载完成 + +2. **运行项目** + - 双击 `run.bat` + - 等待编译和安装 + +### 方法 2:使用命令行 + +打开命令提示符,进入项目目录: + +```bash +cd C:\Users\xdedmi\Desktop\aisee\aisee_flutter + +# 1. 安装依赖 +flutter pub get + +# 2. 检查设备 +flutter devices + +# 3. 运行项目 +flutter run +``` + +### 方法 3:使用 VS Code + +1. 用 VS Code 打开项目文件夹 +2. 按 `F5` 或点击右上角的运行按钮 +3. 选择设备(模拟器或真机) +4. 等待编译和安装 + +## 配置后端地址 + +在运行前,需要配置后端 API 地址。 + +编辑文件:`lib/utils/app_config.dart` + +```dart +class AppConfig { + // 如果使用 Android 模拟器 + static const String apiBaseUrl = 'http://10.0.2.2:8000'; + + // 如果使用真机(改为你的电脑 IP 地址) + // static const String apiBaseUrl = 'http://192.168.1.100:8000'; + + // ... +} +``` + +### 如何获取电脑 IP 地址? + +**Windows:** +```bash +ipconfig +# 查找 "IPv4 地址",例如:192.168.1.100 +``` + +**确保手机和电脑在同一 WiFi 网络!** + +## 首次运行 + +首次运行需要: +1. 下载 Gradle 依赖(约 5-10 分钟) +2. 编译 Android 应用(约 2-3 分钟) +3. 安装到设备 + +**请耐心等待,不要中断!** + +## 运行成功标志 + +看到以下信息说明成功: + +``` +✓ Built build/app/outputs/flutter-apk/app-debug.apk. +Installing build/app/outputs/flutter-apk/app.apk... +Syncing files to device... +Flutter run key commands. +r Hot reload. +R Hot restart. +``` + +应用会自动在设备上打开。 + +## 使用应用 + +1. **授予权限** + - 首次打开会请求相机权限 + - 点击"允许" + +2. **打开相机** + - 点击首页的"打开相机"按钮 + +3. **开始传输** + - 点击"开始传输"按钮 + - 观察状态栏的帧数统计 + +4. **查看效果** + - 捕获帧数:相机捕获的总帧数 + - 上传帧数:成功上传到后端的帧数 + +## 常见问题 + +### 1. Flutter 命令不存在 + +**问题**:`'flutter' 不是内部或外部命令` + +**解决**: +1. 检查 Flutter 是否安装 +2. 检查环境变量是否配置 +3. 重启命令提示符 +4. 参考 `Flutter开发环境搭建.md` + +### 2. 没有可用设备 + +**问题**:`No devices found` + +**解决**: +- 启动 Android 模拟器 +- 或连接真机并开启 USB 调试 + +### 3. Gradle 下载慢 + +**问题**:卡在 `Running Gradle task 'assembleDebug'...` + +**解决**: +1. 配置国内镜像(见环境搭建文档) +2. 使用代理 +3. 耐心等待(首次需要 10-30 分钟) + +### 4. 编译错误 + +**问题**:`FAILURE: Build failed with an exception` + +**解决**: +```bash +# 清理项目 +flutter clean + +# 重新获取依赖 +flutter pub get + +# 重新运行 +flutter run +``` + +### 5. 相机权限被拒绝 + +**问题**:应用显示"相机权限被拒绝" + +**解决**: +1. 进入手机设置 -> 应用管理 -> aisee_flutter +2. 权限 -> 相机 -> 允许 +3. 重启应用 + +### 6. 网络连接失败 + +**问题**:上传图像失败 + +**解决**: +1. 确保后端服务已启动 +2. 检查 `app_config.dart` 中的 IP 地址 +3. 模拟器用 `10.0.2.2`,真机用电脑 IP +4. 确保手机和电脑在同一网络 +5. 关闭防火墙(测试时) + +## 热重载 + +代码修改后,无需重新编译: + +```bash +# 在运行的终端按: +r # 热重载(保留状态) +R # 热重启(重置状态) +q # 退出 +``` + +或在 VS Code 中: +- `Ctrl + S` 保存后自动热重载 +- `Ctrl + F5` 热重启 + +## 调试 + +### 查看日志 + +```bash +flutter logs +``` + +### 查看性能 + +```bash +flutter run --profile +``` + +### 查看设备信息 + +```bash +flutter doctor -v +``` + +## 下一步 + +项目运行成功后: + +1. 测试拍照功能 +2. 测试实时传输 +3. 调整配置参数(分辨率、帧率) +4. 开发后端 API +5. 集成 AI 模型 + +--- + +**需要帮助?** + +查看项目根目录的其他文档: +- `Flutter开发环境搭建.md` - 环境配置 +- `Flutter项目初始化.md` - 项目结构 +- `技术方案.md` - 技术架构 diff --git a/aisee_flutter/手机连接问题.md b/aisee_flutter/手机连接问题.md new file mode 100644 index 0000000..2bd550e --- /dev/null +++ b/aisee_flutter/手机连接问题.md @@ -0,0 +1,104 @@ +# 手机连接问题解决方案 + +## 问题诊断 + +✅ 手机已连接并开启 USB 调试 +❌ Flutter 无法识别 Android 设备 +❌ 原因:Android SDK 未安装或未配置 + +## 解决方案 + +### 方案 1:安装 Android Studio(推荐) + +1. **下载 Android Studio** + - 官网:https://developer.android.com/studio + - 下载最新版本 + +2. **安装 Android Studio** + - 双击安装包 + - 选择 "Standard" 安装 + - 等待下载 Android SDK(约 2-3GB) + +3. **配置 Flutter** + ```bash + # Android Studio 安装完成后,Flutter 会自动检测 + C:\Users\xdedmi\flutter\bin\flutter doctor + ``` + +4. **重新连接手机** + - 拔掉 USB 重新连接 + - 手机上重新授权 USB 调试 + - 运行:`flutter devices` + +### 方案 2:仅安装 Android SDK 命令行工具(快速) + +1. **下载 SDK 命令行工具** + - https://developer.android.com/studio#command-tools + - 下载 "Command line tools only" + +2. **解压到固定位置** + ``` + C:\Android\cmdline-tools\latest\ + ``` + +3. **配置环境变量** + ``` + ANDROID_HOME=C:\Android + Path 添加: + C:\Android\cmdline-tools\latest\bin + C:\Android\platform-tools + ``` + +4. **安装必要组件** + ```bash + sdkmanager "platform-tools" "platforms;android-34" + ``` + +5. **配置 Flutter** + ```bash + C:\Users\xdedmi\flutter\bin\flutter config --android-sdk C:\Android + ``` + +## 快速验证 + +安装完成后运行: + +```bash +# 1. 检查环境 +C:\Users\xdedmi\flutter\bin\flutter doctor + +# 2. 检查设备 +C:\Users\xdedmi\flutter\bin\flutter devices + +# 3. 应该看到你的手机 +# 例如:SM G9980 (mobile) • xxxxxx • android-arm64 • Android 14 +``` + +## 临时解决方案:使用 Web 版本 + +如果暂时无法配置 Android,可以先在浏览器中测试(但相机功能不可用): + +```bash +cd C:\Users\xdedmi\Desktop\aisee\aisee_flutter +C:\Users\xdedmi\flutter\bin\flutter run -d chrome +``` + +## 我的建议 + +**推荐安装 Android Studio**,因为: +1. 一键安装所有必要组件 +2. 包含模拟器管理器 +3. 提供完整的开发工具 +4. 自动配置环境变量 + +安装时间:约 30-60 分钟(包括下载) + +## 下一步 + +1. 选择一个方案开始安装 +2. 安装完成后告诉我 +3. 我会帮你验证并启动项目 + +--- + +你想选择哪个方案?我推荐方案 1(Android Studio)。