add first version
This commit is contained in:
14
.claude/settings.local.json
Normal file
14
.claude/settings.local.json
Normal file
@@ -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:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
103
aisee_flutter/Android设备准备.md
Normal file
103
aisee_flutter/Android设备准备.md
Normal file
@@ -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. 📷 测试相机功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
需要帮助创建模拟器吗?
|
||||||
@@ -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)
|
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||||
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 添加权限 -->
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="aisee_flutter"
|
android:label="aisee_flutter"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
13
aisee_flutter/install.bat
Normal file
13
aisee_flutter/install.bat
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@echo off
|
||||||
|
set PATH=C:\Users\xdedmi\flutter\bin;%PATH%
|
||||||
|
echo 正在安装依赖...
|
||||||
|
cd /d %~dp0
|
||||||
|
flutter pub get
|
||||||
|
echo.
|
||||||
|
echo 依赖安装完成!
|
||||||
|
echo.
|
||||||
|
echo 下一步:
|
||||||
|
echo 1. 连接 Android 设备或启动模拟器
|
||||||
|
echo 2. 双击 run.bat 运行项目
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'screens/camera_screen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@@ -7,116 +8,96 @@ void main() {
|
|||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
// This widget is the root of your application.
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'AISee Camera',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
// This is the theme of your application.
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||||
//
|
useMaterial3: true,
|
||||||
// TRY THIS: Try running your application with "flutter run". You'll see
|
|
||||||
// the application has a purple toolbar. Then, without quitting the app,
|
|
||||||
// try changing the seedColor in the colorScheme below to Colors.green
|
|
||||||
// and then invoke "hot reload" (save your changes or press the "hot
|
|
||||||
// reload" button in a Flutter-supported IDE, or press "r" if you used
|
|
||||||
// the command line to start the app).
|
|
||||||
//
|
|
||||||
// Notice that the counter didn't reset back to zero; the application
|
|
||||||
// state is not lost during the reload. To reset the state, use hot
|
|
||||||
// restart instead.
|
|
||||||
//
|
|
||||||
// This works for code too, not just values: Most code changes can be
|
|
||||||
// tested with just a hot reload.
|
|
||||||
colorScheme: .fromSeed(seedColor: Colors.deepPurple),
|
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
home: const HomeScreen(),
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
const MyHomePage({super.key, required this.title});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
// This widget is the home page of your application. It is stateful, meaning
|
|
||||||
// that it has a State object (defined below) that contains fields that affect
|
|
||||||
// how it looks.
|
|
||||||
|
|
||||||
// This class is the configuration for the state. It holds the values (in this
|
|
||||||
// case the title) provided by the parent (in this case the App widget) and
|
|
||||||
// used by the build method of the State. Fields in a Widget subclass are
|
|
||||||
// always marked "final".
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MyHomePage> createState() => _MyHomePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
|
||||||
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++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
// TRY THIS: Try changing the color here to a specific color (to
|
title: const Text('AISee'),
|
||||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
|
||||||
// change color while the other colors stay the same.
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
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(
|
body: Center(
|
||||||
// Center is a layout widget. It takes a single child and positions it
|
|
||||||
// in the middle of the parent.
|
|
||||||
child: Column(
|
child: Column(
|
||||||
// Column is also a layout widget. It takes a list of children and
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
// 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,
|
|
||||||
children: [
|
children: [
|
||||||
const Text('You have pushed the button this many times:'),
|
const Icon(
|
||||||
Text(
|
Icons.visibility,
|
||||||
'$_counter',
|
size: 100,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
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),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
290
aisee_flutter/lib/screens/camera_screen.dart
Normal file
290
aisee_flutter/lib/screens/camera_screen.dart
Normal file
@@ -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<CameraScreen> createState() => _CameraScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CameraScreenState extends State<CameraScreen> {
|
||||||
|
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<void> _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<void> _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<void> _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<String, dynamic> 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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
aisee_flutter/lib/services/api_service.dart
Normal file
79
aisee_flutter/lib/services/api_service.dart
Normal file
@@ -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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
158
aisee_flutter/lib/services/camera_service.dart
Normal file
158
aisee_flutter/lib/services/camera_service.dart
Normal file
@@ -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<CameraDescription> _cameras = [];
|
||||||
|
bool _isInitialized = false;
|
||||||
|
bool _isStreaming = false;
|
||||||
|
Timer? _captureTimer;
|
||||||
|
|
||||||
|
CameraController? get controller => _controller;
|
||||||
|
bool get isInitialized => _isInitialized;
|
||||||
|
bool get isStreaming => _isStreaming;
|
||||||
|
|
||||||
|
/// 初始化相机
|
||||||
|
Future<void> 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<Uint8List?> 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<Uint8List> _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<void> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
aisee_flutter/lib/utils/app_config.dart
Normal file
13
aisee_flutter/lib/utils/app_config.dart
Normal file
@@ -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 捕获一帧
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.9"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -17,6 +25,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
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:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -33,6 +81,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
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:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +97,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
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:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -49,6 +121,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
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:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -57,6 +145,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -70,11 +174,56 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
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:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -107,6 +256,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -131,6 +288,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
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:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -139,6 +328,150 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -168,6 +501,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
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:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -192,6 +533,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
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:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -208,6 +557,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.0.2"
|
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:
|
sdks:
|
||||||
dart: ">=3.11.0 <4.0.0"
|
dart: ">=3.11.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.38.4"
|
||||||
|
|||||||
@@ -35,6 +35,24 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
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:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
21
aisee_flutter/run.bat
Normal file
21
aisee_flutter/run.bat
Normal file
@@ -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
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
permission_handler_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
253
aisee_flutter/启动指南.md
Normal file
253
aisee_flutter/启动指南.md
Normal file
@@ -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` - 技术架构
|
||||||
104
aisee_flutter/手机连接问题.md
Normal file
104
aisee_flutter/手机连接问题.md
Normal file
@@ -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)。
|
||||||
Reference in New Issue
Block a user