Files
aisee/技术文档/Flutter项目初始化.md
2026-03-02 21:26:32 +08:00

837 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AISee Flutter 项目初始化指南
## 1. 创建项目
```bash
cd C:\Users\xdedmi\Desktop\aisee
# 创建 Flutter 项目
flutter create --org com.aisee --project-name aisee_app --platforms android,ios aisee_app
cd aisee_app
```
## 2. 项目目录结构
```
aisee_app/
├── lib/
│ ├── main.dart # 入口文件
│ ├── app.dart # App 根组件
│ │
│ ├── config/ # 配置
│ │ ├── app_config.dart # 应用配置
│ │ ├── routes.dart # 路由配置
│ │ └── theme.dart # 主题配置
│ │
│ ├── models/ # 数据模型
│ │ ├── detection.dart # 检测结果
│ │ ├── text_region.dart # 文字区域
│ │ ├── scene_description.dart # 场景描述
│ │ └── analysis_result.dart # 分析结果
│ │
│ ├── services/ # 服务层
│ │ ├── api/
│ │ │ ├── api_client.dart # HTTP 客户端
│ │ │ ├── image_api.dart # 图像 API
│ │ │ └── analysis_api.dart # 分析 API
│ │ ├── bluetooth/
│ │ │ ├── bluetooth_service.dart # 蓝牙服务
│ │ │ └── glasses_protocol.dart # 眼镜通信协议
│ │ ├── camera/
│ │ │ └── camera_service.dart # 相机服务
│ │ ├── websocket/
│ │ │ └── ws_service.dart # WebSocket 服务
│ │ └── tts/
│ │ └── tts_service.dart # 语音合成服务
│ │
│ ├── providers/ # 状态管理
│ │ ├── camera_provider.dart
│ │ ├── analysis_provider.dart
│ │ ├── bluetooth_provider.dart
│ │ └── settings_provider.dart
│ │
│ ├── screens/ # 页面
│ │ ├── home/
│ │ │ └── home_screen.dart
│ │ ├── camera/
│ │ │ └── camera_screen.dart
│ │ ├── analysis/
│ │ │ └── analysis_screen.dart
│ │ ├── history/
│ │ │ └── history_screen.dart
│ │ ├── settings/
│ │ │ └── settings_screen.dart
│ │ └── bluetooth/
│ │ └── bluetooth_screen.dart
│ │
│ ├── widgets/ # 通用组件
│ │ ├── detection_overlay.dart # 检测结果叠加层
│ │ ├── loading_indicator.dart
│ │ └── error_widget.dart
│ │
│ └── utils/ # 工具类
│ ├── constants.dart
│ ├── image_utils.dart
│ └── permission_utils.dart
├── assets/ # 静态资源
│ ├── images/
│ ├── icons/
│ └── fonts/
├── test/ # 测试
│ ├── services/
│ ├── providers/
│ └── widgets/
├── android/ # Android 原生代码
├── ios/ # iOS 原生代码
├── pubspec.yaml # 依赖配置
└── analysis_options.yaml # 代码分析配置
```
## 3. 依赖配置 pubspec.yaml
```yaml
name: aisee_app
description: AISee - AI 视觉辅助眼镜系统
publish_to: 'none'
version: 0.1.0+1
environment:
sdk: '>=3.2.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# UI
cupertino_icons: ^1.0.6
google_fonts: ^6.1.0
flutter_svg: ^2.0.9
cached_network_image: ^3.3.0
shimmer: ^3.0.0
# 状态管理
riverpod: ^2.4.9
flutter_riverpod: ^2.4.9
# 路由
go_router: ^13.0.0
# 网络
dio: ^5.4.0
web_socket_channel: ^2.4.0
# 相机
camera: ^0.10.5+7
image: ^4.1.4
# 蓝牙
flutter_blue_plus: ^1.28.5
# 本地存储
sqflite: ^2.3.0
shared_preferences: ^2.2.2
path_provider: ^2.1.1
hive: ^2.2.3
hive_flutter: ^1.1.0
# 权限
permission_handler: ^11.1.0
# 语音
flutter_tts: ^3.8.5
# 图像处理
image_picker: ^1.0.5
# JSON 序列化
json_annotation: ^4.8.1
freezed_annotation: ^2.4.1
# 工具
logger: ^2.0.2+1
intl: ^0.19.0
uuid: ^4.2.1
connectivity_plus: ^5.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
# 代码生成
build_runner: ^2.4.7
json_serializable: ^6.7.1
freezed: ^2.4.5
hive_generator: ^2.0.1
# 测试
mockito: ^5.4.3
mocktail: ^1.0.1
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
fonts:
- family: AISee
fonts:
- asset: assets/fonts/AISee-Regular.ttf
- asset: assets/fonts/AISee-Bold.ttf
weight: 700
```
## 4. 核心代码
### main.dart
```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 强制竖屏
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
// 初始化 Hive 本地存储
await Hive.initFlutter();
runApp(
const ProviderScope(
child: AISeeApp(),
),
);
}
```
### app.dart
```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'config/routes.dart';
import 'config/theme.dart';
class AISeeApp extends ConsumerWidget {
const AISeeApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
title: 'AISee',
theme: AISeeTheme.lightTheme,
darkTheme: AISeeTheme.darkTheme,
themeMode: ThemeMode.system,
routerConfig: router,
debugShowCheckedModeBanner: false,
);
}
}
```
### config/theme.dart
```dart
import 'package:flutter/material.dart';
class AISeeTheme {
static const _primaryColor = Color(0xFF2196F3);
static const _secondaryColor = Color(0xFF00BCD4);
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: _primaryColor,
secondary: _secondaryColor,
brightness: Brightness.light,
),
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
),
);
}
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: _primaryColor,
secondary: _secondaryColor,
brightness: Brightness.dark,
),
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: 0,
),
);
}
}
```
### config/routes.dart
```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../screens/home/home_screen.dart';
import '../screens/camera/camera_screen.dart';
import '../screens/analysis/analysis_screen.dart';
import '../screens/history/history_screen.dart';
import '../screens/settings/settings_screen.dart';
import '../screens/bluetooth/bluetooth_screen.dart';
final routerProvider = Provider<GoRouter>((ref) {
return GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/camera',
builder: (context, state) => const CameraScreen(),
),
GoRoute(
path: '/analysis',
builder: (context, state) => const AnalysisScreen(),
),
GoRoute(
path: '/history',
builder: (context, state) => const HistoryScreen(),
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
GoRoute(
path: '/bluetooth',
builder: (context, state) => const BluetoothScreen(),
),
],
);
});
```
### config/app_config.dart
```dart
class AppConfig {
// API 配置
static const String apiBaseUrl = 'https://api.aisee.com';
static const String wsBaseUrl = 'wss://api.aisee.com/ws';
// 开发环境
static const String devApiBaseUrl = 'http://10.0.2.2:8000';
static const String devWsBaseUrl = 'ws://10.0.2.2:8000/ws';
// 图像配置
static const int imageMaxWidth = 1280;
static const int imageMaxHeight = 720;
static const int imageQuality = 85;
// 蓝牙配置
static const String glassesServiceUuid = '0000ffe0-0000-1000-8000-00805f9b34fb';
static const String glassesCharUuid = '0000ffe1-0000-1000-8000-00805f9b34fb';
// 超时配置
static const Duration apiTimeout = Duration(seconds: 30);
static const Duration wsReconnectDelay = Duration(seconds: 5);
}
```
### services/api/api_client.dart
```dart
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../config/app_config.dart';
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient();
});
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(BaseOptions(
baseUrl: AppConfig.devApiBaseUrl,
connectTimeout: AppConfig.apiTimeout,
receiveTimeout: AppConfig.apiTimeout,
headers: {
'Content-Type': 'application/json',
},
));
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
}
// 上传图像
Future<Map<String, dynamic>> uploadImage(List<int> imageBytes, String filename) async {
final formData = FormData.fromMap({
'file': MultipartFile.fromBytes(imageBytes, filename: filename),
});
final response = await _dio.post('/api/v1/images/upload', data: formData);
return response.data;
}
// 请求 AI 分析
Future<Map<String, dynamic>> analyzeImage(String imageUrl) async {
final response = await _dio.post('/api/v1/analysis/analyze', data: {
'image_url': imageUrl,
});
return response.data;
}
}
```
### services/camera/camera_service.dart
```dart
import 'package:camera/camera.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
final cameraServiceProvider = Provider<CameraService>((ref) {
return CameraService();
});
class CameraService {
final _logger = Logger();
CameraController? _controller;
List<CameraDescription> _cameras = [];
CameraController? get controller => _controller;
bool get isInitialized => _controller?.value.isInitialized ?? false;
Future<void> initialize() async {
_cameras = await availableCameras();
if (_cameras.isEmpty) {
_logger.e('No cameras available');
return;
}
_controller = CameraController(
_cameras.first,
ResolutionPreset.medium,
enableAudio: false,
imageFormatGroup: ImageFormatGroup.jpeg,
);
await _controller!.initialize();
_logger.i('Camera initialized');
}
Future<XFile?> takePicture() async {
if (!isInitialized) return null;
return await _controller!.takePicture();
}
void dispose() {
_controller?.dispose();
}
}
```
### services/bluetooth/bluetooth_service.dart
```dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
import '../../config/app_config.dart';
final bluetoothServiceProvider = Provider<BluetoothService>((ref) {
return BluetoothService();
});
class BluetoothService {
final _logger = Logger();
BluetoothDevice? _connectedDevice;
BluetoothCharacteristic? _writeCharacteristic;
final _dataController = StreamController<List<int>>.broadcast();
Stream<List<int>> get dataStream => _dataController.stream;
bool get isConnected => _connectedDevice != null;
// 扫描设备
Future<List<ScanResult>> scanDevices({Duration timeout = const Duration(seconds: 5)}) async {
final results = <ScanResult>[];
await FlutterBluePlus.startScan(timeout: timeout);
FlutterBluePlus.scanResults.listen((scanResults) {
results.addAll(scanResults);
});
await Future.delayed(timeout);
await FlutterBluePlus.stopScan();
return results;
}
// 连接设备
Future<bool> connect(BluetoothDevice device) async {
try {
await device.connect(timeout: const Duration(seconds: 10));
_connectedDevice = device;
// 发现服务
final services = await device.discoverServices();
for (var service in services) {
if (service.uuid.toString() == AppConfig.glassesServiceUuid) {
for (var char in service.characteristics) {
if (char.uuid.toString() == AppConfig.glassesCharUuid) {
_writeCharacteristic = char;
// 监听数据
await char.setNotifyValue(true);
char.onValueReceived.listen((data) {
_dataController.add(data);
});
}
}
}
}
_logger.i('Connected to ${device.platformName}');
return true;
} catch (e) {
_logger.e('Connection failed: $e');
return false;
}
}
// 发送数据到眼镜
Future<void> sendToGlasses(Map<String, dynamic> data) async {
if (_writeCharacteristic == null) return;
final jsonStr = jsonEncode(data);
final bytes = utf8.encode(jsonStr);
await _writeCharacteristic!.write(bytes);
}
// 断开连接
Future<void> disconnect() async {
await _connectedDevice?.disconnect();
_connectedDevice = null;
_writeCharacteristic = null;
}
void dispose() {
_dataController.close();
disconnect();
}
}
```
### screens/home/home_screen.dart
```dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('AISee'),
actions: [
IconButton(
icon: const Icon(Icons.bluetooth),
onPressed: () => context.push('/bluetooth'),
),
IconButton(
icon: const Icon(Icons.settings),
onPressed: () => context.push('/settings'),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 状态卡片
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Icon(Icons.visibility, size: 48, color: Colors.blue),
const SizedBox(height: 8),
Text(
'AISee 视觉辅助',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 4),
const Text('眼镜未连接', style: TextStyle(color: Colors.grey)),
],
),
),
),
const SizedBox(height: 24),
// 功能按钮
Expanded(
child: GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
children: [
_FeatureCard(
icon: Icons.camera_alt,
label: '拍照识别',
color: Colors.blue,
onTap: () => context.push('/camera'),
),
_FeatureCard(
icon: Icons.text_fields,
label: '文字识别',
color: Colors.green,
onTap: () => context.push('/camera'),
),
_FeatureCard(
icon: Icons.landscape,
label: '场景理解',
color: Colors.orange,
onTap: () => context.push('/camera'),
),
_FeatureCard(
icon: Icons.history,
label: '历史记录',
color: Colors.purple,
onTap: () => context.push('/history'),
),
],
),
),
],
),
),
);
}
}
class _FeatureCard extends StatelessWidget {
final IconData icon;
final String label;
final Color color;
final VoidCallback onTap;
const _FeatureCard({
required this.icon,
required this.label,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 48, color: color),
const SizedBox(height: 8),
Text(label, style: Theme.of(context).textTheme.titleMedium),
],
),
),
);
}
}
```
### 其他页面占位
```dart
// screens/camera/camera_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CameraScreen extends ConsumerWidget {
const CameraScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('拍照识别')),
body: const Center(child: Text('相机页面 - 待开发')),
);
}
}
// screens/analysis/analysis_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AnalysisScreen extends ConsumerWidget {
const AnalysisScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('分析结果')),
body: const Center(child: Text('分析结果页面 - 待开发')),
);
}
}
// screens/history/history_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HistoryScreen extends ConsumerWidget {
const HistoryScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('历史记录')),
body: const Center(child: Text('历史记录页面 - 待开发')),
);
}
}
// screens/settings/settings_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SettingsScreen extends ConsumerWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('设置')),
body: const Center(child: Text('设置页面 - 待开发')),
);
}
}
// screens/bluetooth/bluetooth_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class BluetoothScreen extends ConsumerWidget {
const BluetoothScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('蓝牙连接')),
body: const Center(child: Text('蓝牙连接页面 - 待开发')),
);
}
}
```
## 5. Android 权限配置
### android/app/src/main/AndroidManifest.xml 添加权限
```xml
<!-- 在 <manifest> 标签内添加 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
```
### android/app/build.gradle 修改最低 SDK
```groovy
android {
defaultConfig {
minSdkVersion 21 // 改为 21
}
}
```
## 6. 初始化项目命令
```bash
# 1. 创建项目
cd C:\Users\xdedmi\Desktop\aisee
flutter create --org com.aisee --project-name aisee_app --platforms android,ios aisee_app
cd aisee_app
# 2. 获取依赖
flutter pub get
# 3. 运行代码生成freezed、json_serializable
dart run build_runner build --delete-conflicting-outputs
# 4. 运行项目
flutter run
# 5. 运行测试
flutter test
```
## 7. Git 初始化
```bash
cd C:\Users\xdedmi\Desktop\aisee\aisee_app
git init
git add .
git commit -m "feat: 初始化 AISee Flutter 项目"
```
## 8. 开发顺序建议
```
第 1 周:项目搭建 + 首页 UI
第 2 周:相机模块 + 拍照功能
第 3 周API 对接 + 图像上传
第 4 周AI 分析结果展示
第 5 周:蓝牙通信模块
第 6 周:语音播报 + 历史记录
第 7 周:设置页面 + 优化
第 8 周:测试 + Bug 修复
```
---
**提示**:先运行 `flutter pub get` 安装依赖,然后按照目录结构创建文件,逐步填充代码。