diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6122f521d..97a32b9e3 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,6 +11,9 @@
+
+
+
diff --git a/lib/app/translations/app_translations.dart b/lib/app/translations/app_translations.dart
index 4ccb05710..71bf8b7a0 100644
--- a/lib/app/translations/app_translations.dart
+++ b/lib/app/translations/app_translations.dart
@@ -36,8 +36,9 @@ class AppTranslations extends Translations {
'check_update': '检查更新',
'clear_cache': '清除缓存',
'home': '首页',
- 'explore': '探索',
+ 'explore': '发现',
'profile': '我的',
+ 'device': '设备',
},
'en_US': {
// General
@@ -74,6 +75,7 @@ class AppTranslations extends Translations {
'home': 'Home',
'explore': 'Explore',
'profile': 'Profile',
+ 'device': 'Device',
},
};
}
\ No newline at end of file
diff --git a/lib/core/routes/app_pages.dart b/lib/core/routes/app_pages.dart
index b04ee1937..651b22bd6 100644
--- a/lib/core/routes/app_pages.dart
+++ b/lib/core/routes/app_pages.dart
@@ -22,10 +22,33 @@ import '../../modules/translation/views/translation_view.dart';
import '../../modules/translation/bindings/translation_binding.dart';
import '../../modules/translation/views/translation_history_view.dart';
import '../../modules/translation/bindings/translation_history_binding.dart';
+import '../../modules/login/views/login_view.dart';
+import '../../modules/login/bindings/login_binding.dart';
+import '../../modules/splash/views/splash_view.dart';
+import '../../modules/splash/bindings/splash_binding.dart';
+import '../../modules/pairing/views/pairing_view.dart';
+import '../../modules/pairing/bindings/pairing_binding.dart';
import './app_routes.dart';
+import '../../modules/settings/views/settings_view.dart';
+import '../../modules/settings/bindings/settings_binding.dart';
abstract class AppPages {
static final pages = [
+ GetPage(
+ name: Routes.splash,
+ page: () => const SplashView(),
+ binding: SplashBinding(),
+ ),
+ GetPage(
+ name: Routes.login,
+ page: () => const LoginView(),
+ binding: LoginBinding(),
+ ),
+ GetPage(
+ name: Routes.pairing,
+ page: () => const PairingView(),
+ binding: PairingBinding(),
+ ),
GetPage(
name: Routes.initial,
page: () => const HomeView(),
@@ -101,5 +124,10 @@ abstract class AppPages {
page: () => const TranslationHistoryView(),
binding: TranslationHistoryBinding(),
),
+ GetPage(
+ name: Routes.settings,
+ page: () => const SettingsView(),
+ binding: SettingsBinding(),
+ ),
];
}
\ No newline at end of file
diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart
index c112313bf..1d6c8a1ea 100644
--- a/lib/core/routes/app_routes.dart
+++ b/lib/core/routes/app_routes.dart
@@ -1,9 +1,13 @@
abstract class Routes {
+ static const splash = '/splash';
+ static const login = '/login';
+ static const pairing = '/pairing';
static const initial = '/home';
static const home = '/home';
static const explore = '/explore';
static const chat = '/chat';
static const profile = '/profile';
+ static const settings = '/settings';
static const speechDemo = '/speech_demo';
static const TTS_TEST = '/tts_test';
static const ASR_TEST = '/asr_test';
diff --git a/lib/core/widgets/common_bottom_nav.dart b/lib/core/widgets/common_bottom_nav.dart
index 1b35aed21..015feadfe 100644
--- a/lib/core/widgets/common_bottom_nav.dart
+++ b/lib/core/widgets/common_bottom_nav.dart
@@ -24,7 +24,7 @@ class CommonBottomNav extends StatelessWidget {
Get.offAllNamed(Routes.explore);
break;
case 2:
- Get.offAllNamed(Routes.profile);
+ Get.offAllNamed(Routes.settings);
break;
}
},
@@ -38,8 +38,8 @@ class CommonBottomNav extends StatelessWidget {
label: 'explore'.tr,
),
BottomNavigationBarItem(
- icon: const Icon(Icons.person),
- label: 'profile'.tr,
+ icon: const Icon(Icons.settings),
+ label: 'settings'.tr,
),
],
);
diff --git a/lib/data/services/bluetooth_service.dart b/lib/data/services/bluetooth_service.dart
new file mode 100644
index 000000000..6bed30f5d
--- /dev/null
+++ b/lib/data/services/bluetooth_service.dart
@@ -0,0 +1,300 @@
+import 'dart:async';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+import 'package:get/get.dart';
+import '../../core/utils/logger.dart';
+import 'dart:io' show Platform;
+import 'package:permission_handler/permission_handler.dart';
+
+/// 蓝牙设备服务,用于发现和管理蓝牙设备
+class BluetoothService extends GetxService {
+ static BluetoothService get to => Get.find();
+
+ // 单例实例
+ static final BluetoothService _instance = BluetoothService._internal();
+ factory BluetoothService() => _instance;
+ BluetoothService._internal();
+
+ // 设备状态
+ final _isAvailable = false.obs;
+ final _isOn = false.obs;
+
+ // 已连接的设备
+ final _connectedDevices = [].obs;
+
+ // 扫描状态
+ final _isScanning = false.obs;
+
+ // 扫描结果
+ final _scanResults = [].obs;
+
+ // 当前选中的设备
+ final _selectedDevice = Rx(null);
+
+ // 连接状态
+ final _connectionState = Rx(BluetoothConnectionState.disconnected);
+
+ // 订阅
+ StreamSubscription? _stateSubscription;
+ StreamSubscription? _scanSubscription;
+ StreamSubscription? _connectionSubscription;
+
+ // 权限状态
+ final _permissionsGranted = false.obs;
+ bool get permissionsGranted => _permissionsGranted.value;
+
+ // Getters
+ bool get isAvailable => _isAvailable.value;
+ bool get isOn => _isOn.value;
+ bool get isScanning => _isScanning.value;
+ List get connectedDevices => _connectedDevices;
+ List get scanResults => _scanResults;
+ BluetoothDevice? get selectedDevice => _selectedDevice.value;
+ BluetoothConnectionState get connectionState => _connectionState.value;
+
+ // Observable getters (for reactive programming)
+ RxBool get isAvailableObs => _isAvailable;
+ RxBool get isOnObs => _isOn;
+ RxList get connectedDevicesObs => _connectedDevices;
+ RxList get scanResultsObs => _scanResults;
+ Rx get selectedDeviceObs => _selectedDevice;
+ Rx get connectionStateObs => _connectionState;
+
+ /// 初始化蓝牙服务
+ Future init() async {
+ try {
+ // 不在初始化时检查权限,改为在需要时才检查
+ // await _checkAndRequestPermissions();
+
+ // 监听蓝牙状态
+ _stateSubscription = FlutterBluePlus.adapterState.listen((state) {
+ _isOn.value = state == BluetoothAdapterState.on;
+ _isAvailable.value = state != BluetoothAdapterState.unavailable;
+
+ if (state == BluetoothAdapterState.on) {
+ // 蓝牙打开时,获取已连接设备,但不请求权限
+ // 只有在用户主动扫描时才请求权限
+ _getConnectedDevices();
+ } else if (state == BluetoothAdapterState.off) {
+ // 蓝牙关闭时,清空设备列表
+ _connectedDevices.clear();
+ _scanResults.clear();
+ _selectedDevice.value = null;
+ }
+ });
+
+ // 检查当前蓝牙状态,如果已开启则获取设备
+ if (await FlutterBluePlus.isOn) {
+ _isOn.value = true;
+ _isAvailable.value = true;
+ _getConnectedDevices();
+ }
+
+ return this;
+ } catch (e) {
+ Logger.error('初始化蓝牙服务失败: $e');
+ return this;
+ }
+ }
+
+ /// 检查并请求蓝牙所需权限
+ Future _checkAndRequestPermissions() async {
+ // 暂时跳过权限检查,直接返回true
+ _permissionsGranted.value = true;
+ return true;
+ }
+
+ /// 再次请求权限
+ Future requestPermissions() async {
+ // 暂时跳过权限检查,直接返回true
+ _permissionsGranted.value = true;
+ return true;
+ }
+
+ /// 获取已连接的设备
+ Future _getConnectedDevices() async {
+ try {
+ // 获取已连接的设备
+ List devices = await FlutterBluePlus.connectedSystemDevices;
+ _connectedDevices.value = devices;
+
+ // 如果有已连接的设备但没有选中设备,则选中第一个
+ if (_connectedDevices.isNotEmpty && _selectedDevice.value == null) {
+ _selectedDevice.value = _connectedDevices[0];
+ _monitorConnectionState(_selectedDevice.value!);
+ }
+
+ Logger.info('已连接设备数量: ${_connectedDevices.length}');
+ } catch (e) {
+ Logger.error('获取已连接设备失败: $e');
+ }
+ }
+
+ /// 获取系统中已配对的设备
+ Future> getPairedDevices() async {
+ try {
+ // 获取已配对的设备(系统已知设备)
+ List systemDevices = await FlutterBluePlus.bondedDevices;
+ return systemDevices;
+ } catch (e) {
+ Logger.error('获取已配对设备失败: $e');
+ return [];
+ }
+ }
+
+ /// 开始扫描附近的蓝牙设备
+ Future startScan({Duration? timeout}) async {
+ if (_isScanning.value) return;
+
+ // 清空旧的扫描结果
+ _scanResults.clear();
+
+ try {
+ // 开始扫描
+ _scanSubscription?.cancel();
+ _scanSubscription = FlutterBluePlus.scanResults.listen((results) {
+ _scanResults.value = results;
+ });
+
+ // 设置扫描中状态
+ _isScanning.value = true;
+
+ // 开始扫描
+ await FlutterBluePlus.startScan(
+ timeout: timeout ?? const Duration(seconds: 10),
+ );
+
+ // 扫描完成后更新状态
+ _isScanning.value = false;
+
+ Logger.info('蓝牙扫描完成,发现 ${_scanResults.length} 个设备');
+ } catch (e) {
+ _isScanning.value = false;
+ Logger.error('蓝牙扫描失败: $e');
+ }
+ }
+
+ /// 停止扫描
+ Future stopScan() async {
+ if (!_isScanning.value) return;
+
+ try {
+ await FlutterBluePlus.stopScan();
+ _isScanning.value = false;
+ } catch (e) {
+ Logger.error('停止蓝牙扫描失败: $e');
+ }
+ }
+
+ /// 连接到指定设备
+ Future connectToDevice(BluetoothDevice device) async {
+ if (_selectedDevice.value == device) {
+ // 如果是当前选中设备,检查连接状态
+ if (_connectionState.value == BluetoothConnectionState.connected) {
+ Logger.info('设备 ${device.platformName} 已经连接,无需重新连接');
+ return true;
+ }
+ }
+
+ try {
+ // 检查设备是否已连接
+ bool isAlreadyConnected = false;
+ // 检查设备连接状态
+ BluetoothConnectionState state = await device.connectionState.first;
+ if (state == BluetoothConnectionState.connected) {
+ Logger.info('设备 ${device.platformName} 已处于连接状态');
+ isAlreadyConnected = true;
+ }
+
+ // 如果设备已连接,直接更新状态而不尝试连接
+ if (isAlreadyConnected) {
+ // 断开当前跟踪的设备(如果有)
+ if (_selectedDevice.value != null && _selectedDevice.value != device) {
+ _connectionSubscription?.cancel();
+ }
+
+ // 更新状态
+ _selectedDevice.value = device;
+ _monitorConnectionState(device);
+
+ return true;
+ }
+
+ // 断开当前连接
+ await disconnectFromDevice();
+
+ // 连接新设备
+ await device.connect();
+
+ // 更新状态
+ _selectedDevice.value = device;
+ _monitorConnectionState(device);
+
+ return true;
+ } catch (e) {
+ Logger.error('连接蓝牙设备失败: $e');
+ return false;
+ }
+ }
+
+ /// 断开与当前设备的连接
+ Future disconnectFromDevice() async {
+ if (_selectedDevice.value == null) return true;
+
+ try {
+ await _selectedDevice.value!.disconnect();
+ _connectionSubscription?.cancel();
+ _connectionSubscription = null;
+ _selectedDevice.value = null;
+
+ return true;
+ } catch (e) {
+ Logger.error('断开蓝牙设备连接失败: $e');
+ return false;
+ }
+ }
+
+ /// 监控连接状态
+ void _monitorConnectionState(BluetoothDevice device) {
+ _connectionSubscription?.cancel();
+ _connectionSubscription = device.connectionState.listen((state) {
+ _connectionState.value = state;
+
+ if (state == BluetoothConnectionState.disconnected) {
+ _selectedDevice.value = null;
+ }
+ });
+ }
+
+ /// 获取已连接设备的信息
+ Map getConnectedDeviceInfo() {
+ if (_selectedDevice.value == null) {
+ return {
+ 'connected': false,
+ 'device': null,
+ };
+ }
+
+ return {
+ 'connected': _connectionState.value == BluetoothConnectionState.connected,
+ 'device': {
+ 'name': _selectedDevice.value!.platformName.isEmpty ?
+ '未命名设备' : _selectedDevice.value!.platformName,
+ 'id': _selectedDevice.value!.remoteId.str,
+ 'isConnected': _connectionState.value == BluetoothConnectionState.connected,
+ 'rssi': _scanResults
+ .firstWhereOrNull((r) => r.device.remoteId == _selectedDevice.value!.remoteId)
+ ?.rssi
+ }
+ };
+ }
+
+ @override
+ void onClose() {
+ _stateSubscription?.cancel();
+ _scanSubscription?.cancel();
+ _connectionSubscription?.cancel();
+ disconnectFromDevice();
+ super.onClose();
+ }
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index 2304c63b9..3ddff4655 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -12,8 +12,18 @@ import 'app/translations/app_translations.dart';
import 'data/services/voice_interaction_service.dart';
void main() async {
+ // 确保Flutter绑定初始化
WidgetsFlutterBinding.ensureInitialized();
+ // 先运行应用,显示启动页面
+ runApp(const MainApp());
+
+ // 然后异步执行其他初始化操作
+ _initializeApp();
+}
+
+// 异步初始化应用
+Future _initializeApp() async {
// 设置窗口大小(仅在 macOS 下生效)
if (Platform.isMacOS) {
const double width = 375.0;
@@ -46,7 +56,7 @@ void main() async {
// AudioService 将在 AudioServiceManager 中初始化
// 不再需要在这里调用 AudioService.init
- runApp(const MainApp());
+ // 其他可能的初始化操作...
}
class MainApp extends StatelessWidget {
@@ -66,7 +76,7 @@ class MainApp extends StatelessWidget {
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialBinding: InitialBinding(),
- initialRoute: Routes.initial,
+ initialRoute: Routes.splash,
getPages: AppPages.pages,
debugShowCheckedModeBanner: false,
translations: AppTranslations(),
diff --git a/lib/modules/explore/controllers/explore_controller.dart b/lib/modules/explore/controllers/explore_controller.dart
index a13432868..bbca18d55 100644
--- a/lib/modules/explore/controllers/explore_controller.dart
+++ b/lib/modules/explore/controllers/explore_controller.dart
@@ -1,32 +1,170 @@
import 'package:get/get.dart';
-import '../../../data/providers/agent_provider.dart';
-import '../../../data/services/voice_interaction_service.dart';
-class ChatItem {
- final String id;
+import 'package:flutter/material.dart';
- ChatItem({
- required this.id,
+class AIFeature {
+ final String title;
+ final String description;
+ final IconData icon;
+ final Color startColor;
+ final Color endColor;
+ final Color iconBgColor;
+ final Color iconColor;
+
+ AIFeature({
+ required this.title,
+ required this.description,
+ required this.icon,
+ required this.startColor,
+ required this.endColor,
+ required this.iconBgColor,
+ required this.iconColor,
+ });
+}
+
+class FunctionScene {
+ final String title;
+ final String description;
+ final IconData icon;
+ final Color startColor;
+ final Color endColor;
+ final Color iconColor;
+
+ FunctionScene({
+ required this.title,
+ required this.description,
+ required this.icon,
+ required this.startColor,
+ required this.endColor,
+ required this.iconColor,
});
}
class ExploreController extends GetxController {
- static ExploreController get to => Get.find();
+ // 搜索关键词
+ final searchController = TextEditingController();
- final _chatList = [].obs;
+ // AI功能列表
+ final List aiFeatures = [
+ AIFeature(
+ title: '同声翻译',
+ description: '实时翻译40+种语言',
+ icon: Icons.translate,
+ startColor: Colors.blue.shade100,
+ endColor: Colors.blue.shade200,
+ iconBgColor: Colors.blue.withOpacity(0.2),
+ iconColor: Colors.blue.shade600,
+ ),
+ AIFeature(
+ title: 'AI女友',
+ description: '智能陪伴,随时聊天',
+ icon: Icons.favorite,
+ startColor: Colors.pink.shade100,
+ endColor: Colors.pink.shade200,
+ iconBgColor: Colors.pink.withOpacity(0.2),
+ iconColor: Colors.pink.shade600,
+ ),
+ AIFeature(
+ title: '音乐创作',
+ description: '用AI创作专属音乐',
+ icon: Icons.music_note,
+ startColor: Colors.green.shade100,
+ endColor: Colors.green.shade200,
+ iconBgColor: Colors.green.withOpacity(0.2),
+ iconColor: Colors.green.shade600,
+ ),
+ AIFeature(
+ title: '声音克隆',
+ description: '创建你的数字声音',
+ icon: Icons.mic,
+ startColor: Colors.amber.shade100,
+ endColor: Colors.amber.shade200,
+ iconBgColor: Colors.amber.withOpacity(0.2),
+ iconColor: Colors.amber.shade600,
+ ),
+ ];
- List get chatList => _chatList;
+ // 热门功能场景列表
+ final List functionScenes = [
+ FunctionScene(
+ title: '商务会议助手',
+ description: '实时记录会议纪要,多语言翻译',
+ icon: Icons.people,
+ startColor: Colors.blue.shade200,
+ endColor: Colors.blue.shade300,
+ iconColor: Colors.blue.shade600,
+ ),
+ FunctionScene(
+ title: '语言学习助手',
+ description: '发音纠正,实时练习口语',
+ icon: Icons.school,
+ startColor: Colors.green.shade200,
+ endColor: Colors.green.shade300,
+ iconColor: Colors.green.shade600,
+ ),
+ FunctionScene(
+ title: '旅行翻译助手',
+ description: '实时语音翻译,旅游景点介绍',
+ icon: Icons.language,
+ startColor: Colors.amber.shade200,
+ endColor: Colors.amber.shade300,
+ iconColor: Colors.amber.shade600,
+ ),
+ ];
- @override
- void onInit() {
- super.onInit();
- _loadInitialChats();
+ // 搜索功能
+ void search() {
+ if (searchController.text.isEmpty) return;
+
+ Get.snackbar(
+ '搜索',
+ '正在搜索: ${searchController.text}',
+ snackPosition: SnackPosition.TOP,
+ );
+
+ // 实际应用中,这里应该实现真正的搜索逻辑
+ searchController.clear();
}
- void _loadInitialChats() {
- final agents = AgentProvider.getAllAgents();
- _chatList.assignAll(
- agents.map((agent) => ChatItem(id: agent.id)).toList(),
+ // 点击AI功能
+ void onAIFeatureTap(int index) {
+ if (index < 0 || index >= aiFeatures.length) return;
+
+ Get.snackbar(
+ '功能',
+ '您选择了: ${aiFeatures[index].title}',
+ snackPosition: SnackPosition.TOP,
);
+
+ // 实际应用中,这里应该跳转到对应的功能页面
}
+ // 点击功能场景
+ void onFunctionSceneTap(int index) {
+ if (index < 0 || index >= functionScenes.length) return;
+
+ Get.snackbar(
+ '场景',
+ '您选择了: ${functionScenes[index].title}',
+ snackPosition: SnackPosition.TOP,
+ );
+
+ // 实际应用中,这里应该跳转到对应的场景页面
+ }
+
+ // 尝试新功能
+ void tryNewFeature() {
+ Get.snackbar(
+ '新功能',
+ '您选择了尝试AI歌手模式',
+ snackPosition: SnackPosition.TOP,
+ );
+
+ // 实际应用中,这里应该跳转到新功能页面
+ }
+
+ @override
+ void onClose() {
+ searchController.dispose();
+ super.onClose();
+ }
}
\ No newline at end of file
diff --git a/lib/modules/explore/views/explore_view.dart b/lib/modules/explore/views/explore_view.dart
index 3916ed86f..15651717a 100644
--- a/lib/modules/explore/views/explore_view.dart
+++ b/lib/modules/explore/views/explore_view.dart
@@ -1,111 +1,312 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'dart:ui';
import '../controllers/explore_controller.dart';
-import '../../chat/controllers/chat_controller.dart';
-import '../../chat/views/chat_view.dart';
-import '../../../data/providers/agent_provider.dart';
import '../../../core/widgets/common_bottom_nav.dart';
-import '../../../core/routes/app_routes.dart';
class ExploreView extends GetView {
const ExploreView({Key? key}) : super(key: key);
- void _onChatItemTap(ChatItem item) {
- // 获取对应的Agent
- final agent = AgentProvider.getAgentById(item.id);
- if (agent == null) {
- Get.snackbar(
- 'Error',
- 'Agent not found',
- snackPosition: SnackPosition.BOTTOM,
- );
- return;
- }
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.grey[50],
+ body: SafeArea(
+ child: Column(
+ children: [
+ // 标题和搜索
+ _buildHeaderAndSearch(),
+
+ // 主要内容(可滚动)
+ Expanded(
+ child: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ child: Padding(
+ padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 80.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // 特色功能
+ _buildFeaturedCard(),
+
+ // AI功能列表
+ SizedBox(height: 24.h),
+ _buildAIFeaturesSection(),
+
+ // 热门功能场景
+ SizedBox(height: 24.h),
+ _buildFunctionScenesSection(),
+
+ // 新功能推荐
+ SizedBox(height: 24.h),
+ _buildNewFeatureSection(),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ bottomNavigationBar: const CommonBottomNav(currentIndex: 1),
+ );
+ }
- // 检查是否是同声翻译官
- if (agent.id == 'simultaneous_interpreter') {
- // 导航到翻译页面,并传递代理信息
- Get.toNamed(Routes.translation, arguments: {
- 'agentId': agent.id,
- 'agentName': agent.name,
- 'agentAvatar': agent.avatarUrl,
- });
- return;
- }
+ // 标题和搜索
+ Widget _buildHeaderAndSearch() {
+ return Padding(
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ children: [
+ // 标题和菜单按钮
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ '发现',
+ style: TextStyle(
+ fontSize: 24.sp,
+ fontWeight: FontWeight.bold,
+ color: Colors.grey[800],
+ ),
+ ),
+ Container(
+ width: 32.w,
+ height: 32.w,
+ decoration: BoxDecoration(
+ color: Colors.grey[200],
+ shape: BoxShape.circle,
+ ),
+ child: IconButton(
+ icon: Icon(Icons.more_vert, size: 16.sp),
+ padding: EdgeInsets.zero,
+ onPressed: () {},
+ ),
+ ),
+ ],
+ ),
+
+ // 搜索框
+ SizedBox(height: 16.h),
+ Container(
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: TextField(
+ controller: controller.searchController,
+ decoration: InputDecoration(
+ hintText: '搜索AI功能...',
+ hintStyle: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[400],
+ ),
+ prefixIcon: Icon(
+ Icons.search,
+ size: 20.sp,
+ color: Colors.grey[400],
+ ),
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.symmetric(vertical: 12.h),
+ ),
+ textInputAction: TextInputAction.search,
+ onSubmitted: (_) => controller.search(),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ // 特色功能卡片
+ Widget _buildFeaturedCard() {
+ return Container(
+ height: 180.h,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(24.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.2),
+ blurRadius: 10,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ child: Stack(
+ children: [
+ // 背景图片
+ ClipRRect(
+ borderRadius: BorderRadius.circular(24.r),
+ child: Container(
+ color: Colors.blue.shade900, // 预设颜色,图片加载失败时显示
+ child: Image.network(
+ 'https://images.unsplash.com/photo-1526378722484-bd91ca387e72?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80',
+ width: double.infinity,
+ height: 180.h,
+ fit: BoxFit.cover,
+ errorBuilder: (context, error, stackTrace) {
+ return Container(
+ color: Colors.blue.shade900,
+ width: double.infinity,
+ height: 180.h,
+ child: Icon(
+ Icons.image_not_supported,
+ color: Colors.white,
+ size: 40.sp,
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+
+ // 渐变遮罩
+ Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(24.r),
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Colors.black.withOpacity(0.1),
+ Colors.black.withOpacity(0.6),
+ ],
+ ),
+ ),
+ ),
+
+ // 文本内容
+ Padding(
+ padding: EdgeInsets.all(24.w),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '智能耳机,无限可能',
+ style: TextStyle(
+ fontSize: 22.sp,
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Text(
+ '探索DeepVoice提供的AI功能',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.white.withOpacity(0.8),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
- // 导航到聊天页面,让 binding 来处理控制器的创建
- Get.toNamed('/chat', arguments: {
- 'agentId': agent.id,
- 'agentName': agent.name,
- 'agentVoice': agent.voice,
- 'agentAvatar': agent.avatarUrl,
- 'agentSubtitle': agent.description,
- 'systemPrompt': agent.systemPrompt,
- 'welcomeMessage': agent.welcomeMessage,
- 'playVoiceOnEnter': false,
- });
+ // AI功能部分
+ Widget _buildAIFeaturesSection() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'AI功能',
+ style: TextStyle(
+ fontSize: 18.sp,
+ fontWeight: FontWeight.w600,
+ color: Colors.grey[800],
+ ),
+ ),
+ SizedBox(height: 16.h),
+ GridView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 2,
+ crossAxisSpacing: 16.w,
+ mainAxisSpacing: 16.h,
+ childAspectRatio: 1.0,
+ ),
+ itemCount: controller.aiFeatures.length,
+ itemBuilder: (context, index) {
+ final feature = controller.aiFeatures[index];
+ return _buildAIFeatureItem(feature, index);
+ },
+ ),
+ ],
+ );
}
- Widget _buildChatItem(ChatItem item) {
- // 获取对应的Agent
- final agent = AgentProvider.getAgentById(item.id);
- if (agent == null) return const SizedBox.shrink();
-
- return InkWell(
- onTap: () => _onChatItemTap(item),
+ // AI功能项
+ Widget _buildAIFeatureItem(AIFeature feature, int index) {
+ return GestureDetector(
+ onTap: () => controller.onAIFeatureTap(index),
child: Container(
- padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
- child: Row(
+ padding: EdgeInsets.all(20.w),
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ feature.startColor,
+ feature.endColor,
+ ],
+ ),
+ borderRadius: BorderRadius.circular(20.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Stack(
children: [
- // 头像
- CircleAvatar(
- radius: 25.r,
- backgroundImage: AssetImage(agent.avatarUrl),
- backgroundColor: const Color(0xFFEEF4FF),
- ),
- SizedBox(width: 12.w),
- // 聊天内容
- Expanded(
+ // 图标圆圈
+ Align(
+ alignment: Alignment.topLeft,
+ child: Container(
+ width: 48.w,
+ height: 48.w,
+ decoration: BoxDecoration(
+ color: feature.iconColor.withOpacity(0.2),
+ shape: BoxShape.circle,
+ ),
+ child: Icon(
+ feature.icon,
+ size: 24.sp,
+ color: feature.iconColor,
+ ),
+ ),
+ ),
+ // 文本内容
+ Padding(
+ padding: EdgeInsets.only(top: 58.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
children: [
- // 名称行
- Row(
- children: [
- Text(
- agent.name,
- style: TextStyle(
- fontSize: 16.sp,
- fontWeight: FontWeight.w600,
- color: Colors.black,
- ),
- ),
- if (agent.tag == '推荐') ...[
- SizedBox(width: 8.w),
- Container(
- padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
- decoration: BoxDecoration(
- color: const Color(0xFFE8F5E9),
- borderRadius: BorderRadius.circular(4.r),
- ),
- child: Text(
- '推荐',
- style: TextStyle(
- fontSize: 12.sp,
- color: const Color(0xFF4CAF50),
- ),
- ),
- ),
- ],
- ],
+ // 标题
+ Text(
+ feature.title,
+ style: TextStyle(
+ fontSize: 18.sp,
+ fontWeight: FontWeight.w600,
+ color: Colors.grey[800],
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
// 描述
Text(
- agent.description,
+ feature.description,
style: TextStyle(
- fontSize: 14.sp,
+ fontSize: 13.sp,
color: Colors.grey[600],
),
maxLines: 1,
@@ -120,53 +321,191 @@ class ExploreView extends GetView {
);
}
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Colors.white,
- appBar: AppBar(
- backgroundColor: Colors.white,
- elevation: 0,
- title: Text(
- '对话',
+ // 热门功能场景部分
+ Widget _buildFunctionScenesSection() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '热门功能场景',
style: TextStyle(
- fontSize: 17.sp,
+ fontSize: 18.sp,
fontWeight: FontWeight.w600,
- color: Colors.black,
+ color: Colors.grey[800],
),
),
- centerTitle: true,
- actions: [
- IconButton(
- icon: Icon(
- Icons.search,
- color: Colors.black,
- size: 24.sp,
+ SizedBox(height: 16.h),
+ ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: controller.functionScenes.length,
+ itemBuilder: (context, index) {
+ final scene = controller.functionScenes[index];
+ return Padding(
+ padding: EdgeInsets.only(bottom: 16.h),
+ child: _buildFunctionSceneItem(scene, index),
+ );
+ },
+ ),
+ ],
+ );
+ }
+
+ // 功能场景项
+ Widget _buildFunctionSceneItem(FunctionScene scene, int index) {
+ return GestureDetector(
+ onTap: () => controller.onFunctionSceneTap(index),
+ child: Container(
+ padding: EdgeInsets.all(16.w),
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Row(
+ children: [
+ // 图标
+ Container(
+ width: 48.w,
+ height: 48.w,
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ scene.startColor,
+ scene.endColor,
+ ],
+ ),
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Icon(
+ scene.icon,
+ size: 24.sp,
+ color: scene.iconColor,
+ ),
+ ),
+ SizedBox(width: 16.w),
+
+ // 文本内容
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ scene.title,
+ style: TextStyle(
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w600,
+ color: Colors.grey[800],
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Text(
+ scene.description,
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[600],
+ ),
+ ),
+ ],
+ ),
),
- onPressed: () {},
+ ],
+ ),
+ ),
+ );
+ }
+
+ // 新功能推荐部分
+ Widget _buildNewFeatureSection() {
+ return Container(
+ padding: EdgeInsets.all(20.w),
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ Colors.purple.shade100,
+ Colors.purple.shade200,
+ ],
+ ),
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 8,
+ offset: const Offset(0, 4),
),
- IconButton(
- icon: Icon(
- Icons.add_circle_outline,
- color: Colors.black,
+ ],
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // 图标
+ Container(
+ width: 48.w,
+ height: 48.w,
+ decoration: BoxDecoration(
+ color: Colors.purple.shade200,
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Icon(
+ Icons.lightbulb_outline,
size: 24.sp,
+ color: Colors.purple.shade600,
+ ),
+ ),
+ SizedBox(height: 16.h),
+
+ // 标题
+ Text(
+ 'AI歌手模式',
+ style: TextStyle(
+ fontSize: 20.sp,
+ fontWeight: FontWeight.bold,
+ color: Colors.grey[800],
+ ),
+ ),
+ SizedBox(height: 8.h),
+
+ // 描述
+ Text(
+ '用你喜欢的歌手声音演唱任何歌曲,甚至是你自己创作的歌曲',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[600],
+ ),
+ ),
+ SizedBox(height: 16.h),
+
+ // 按钮
+ ElevatedButton(
+ onPressed: controller.tryNewFeature,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.purple.shade500,
+ foregroundColor: Colors.white,
+ padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ ),
+ child: Text(
+ '立即尝试',
+ style: TextStyle(
+ fontSize: 14.sp,
+ fontWeight: FontWeight.w500,
+ ),
),
- onPressed: () {},
),
- SizedBox(width: 4.w),
],
),
- body: Obx(() => ListView.separated(
- itemCount: controller.chatList.length,
- separatorBuilder: (context, index) => Divider(
- height: 1,
- indent: 16.w,
- endIndent: 16.w,
- color: const Color(0xFFEEEEEE),
- ),
- itemBuilder: (context, index) => _buildChatItem(controller.chatList[index]),
- )),
- bottomNavigationBar: const CommonBottomNav(currentIndex: 1),
);
}
}
\ No newline at end of file
diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart
index ef1b5ca81..f29767595 100644
--- a/lib/modules/home/controllers/home_controller.dart
+++ b/lib/modules/home/controllers/home_controller.dart
@@ -1,131 +1,467 @@
import 'package:get/get.dart';
-import 'dart:async';
-import 'dart:io';
+import '../../../core/routes/app_routes.dart';
+import '../../../data/services/bluetooth_service.dart';
+import '../../../core/utils/logger.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService;
import 'package:flutter/material.dart';
-import '../../../data/services/voice_interaction_service.dart';
-enum ConnectStatus {
- initial, // 初始状态,显示添加设备
- searching, // 搜索中
- connected, // 连接成功
- failed // 连接失败
+// 连接状态枚举
+enum ConnectionStatus {
+ disconnected, // 未连接
+ connecting, // 连接中
+ connected, // 已连接
+ failed // 连接失败
}
class HomeController extends GetxController {
static HomeController get to => Get.find();
- final _connectStatus = ConnectStatus.initial.obs;
- final _searchDots = ''.obs;
- Timer? _animationTimer;
- bool _isDisposed = false;
+ // 设备连接状态
+ final _isConnected = false.obs;
+ bool get isConnected => _isConnected.value;
+
+ // 连接状态
+ final _connectionStatus = ConnectionStatus.disconnected.obs;
+ ConnectionStatus get connectionStatus => _connectionStatus.value;
+
+ // 电池状态
+ final _leftEarbudBattery = 75.obs;
+ final _rightEarbudBattery = 52.obs;
+ final _caseBattery = 92.obs;
+
+ int get leftEarbudBattery => _leftEarbudBattery.value;
+ int get rightEarbudBattery => _rightEarbudBattery.value;
+ int get caseBattery => _caseBattery.value;
+
+ // 固件版本
+ final String firmwareVersion = 'v2.4.1';
+
+ // 蓝牙设备信息
+ final _deviceInfo = {}.obs;
+ Map get deviceInfo => _deviceInfo.value;
+
+ // 蓝牙服务
+ BluetoothService? _bluetoothService;
+
+ // 连接错误信息
+ final _connectionErrorMsg = ''.obs;
+ String get connectionErrorMsg => _connectionErrorMsg.value;
- ConnectStatus get connectStatus => _connectStatus.value;
- String get searchDots => _searchDots.value;
-
@override
void onInit() {
super.onInit();
- _connectStatus.value = ConnectStatus.initial;
- VoiceInteractionService.to.initialize();
+ // 检查是否有传入的蓝牙设备信息
+ if (Get.arguments != null && Get.arguments is Map) {
+ _deviceInfo.assignAll(Get.arguments);
+ _updateConnectionStatus();
+ }
+
+ // 获取蓝牙服务
+ _initBluetoothService();
+
+ // 模拟定期获取电池状态
+ _simulateBatteryUpdates();
}
-
-
- @override
- void onClose() {
- _isDisposed = true;
- stopSearchingAnimation();
- super.onClose();
+
+ // 初始化蓝牙服务
+ void _initBluetoothService() {
+ try {
+ if (Get.isRegistered()) {
+ _bluetoothService = Get.find();
+
+ // 如果已经有连接的设备但没有设备信息
+ if (_deviceInfo.isEmpty && _bluetoothService!.selectedDevice != null) {
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
+ _updateConnectionStatus();
+ } else if (_deviceInfo.isEmpty) {
+ // 如果没有设备信息,尝试获取已配对设备
+ _checkPairedDevices();
+ }
+
+ // 监听连接状态变化
+ ever(_bluetoothService!.connectionStateObs, (state) {
+ _processConnectionStateChange(state);
+ });
+
+ // 监听选中设备变化
+ ever(_bluetoothService!.selectedDeviceObs, (device) {
+ if (device != null) {
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
+ _updateConnectionStatus();
+ } else if (_isConnected.value) {
+ // 如果设备断开连接,更新状态
+ _isConnected.value = false;
+ _deviceInfo.clear();
+ _connectionStatus.value = ConnectionStatus.disconnected;
+ }
+ });
+ }
+ } catch (e) {
+ Logger.error('获取蓝牙服务失败: $e');
+ _connectionStatus.value = ConnectionStatus.failed;
+ _connectionErrorMsg.value = '获取蓝牙服务失败';
+ }
}
-
- void startSearchingAnimation() {
- stopSearchingAnimation(); // Clear any existing timer
+
+ // 处理连接状态变化
+ void _processConnectionStateChange(BluetoothConnectionState state) {
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
- if (_isDisposed) return;
+ switch (state) {
+ case BluetoothConnectionState.connected:
+ _connectionStatus.value = ConnectionStatus.connected;
+ _connectionErrorMsg.value = '';
+ _showConnectionSuccessMessage();
+ break;
+ case BluetoothConnectionState.disconnected:
+ _connectionStatus.value = ConnectionStatus.disconnected;
+ break;
+ case BluetoothConnectionState.connecting:
+ _connectionStatus.value = ConnectionStatus.connecting;
+ break;
+ default:
+ // 其他状态暂不处理
+ break;
+ }
- int count = 0;
- _animationTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
- if (_isDisposed || _connectStatus.value != ConnectStatus.searching) {
- timer.cancel();
- return;
+ _updateConnectionStatus();
+ }
+
+ // 显示连接成功消息
+ void _showConnectionSuccessMessage() {
+ if (_bluetoothService?.selectedDevice != null) {
+ String deviceName = _bluetoothService!.selectedDevice!.platformName.isEmpty ?
+ '未命名设备' : _bluetoothService!.selectedDevice!.platformName;
+ Get.snackbar(
+ '连接成功',
+ '已成功连接到设备: $deviceName',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.green[100],
+ colorText: Colors.green[800],
+ duration: const Duration(seconds: 2),
+ );
+ }
+ }
+
+ // 尝试查找并连接已配对设备
+ Future _checkPairedDevices() async {
+ try {
+ if (_bluetoothService != null) {
+ // 更新状态为正在连接
+ _connectionStatus.value = ConnectionStatus.connecting;
+
+ // 如果已有选中的设备并且已连接,直接使用
+ if (_bluetoothService!.selectedDevice != null &&
+ _bluetoothService!.connectionState == BluetoothConnectionState.connected) {
+ Logger.info('使用已连接的设备: ${_bluetoothService!.selectedDevice!.platformName}');
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
+ _connectionStatus.value = ConnectionStatus.connected;
+ _updateConnectionStatus();
+ return;
+ }
+
+ // 获取已配对设备
+ final pairedDevices = await _bluetoothService!.getPairedDevices();
+ if (pairedDevices.isEmpty) {
+ Logger.info('没有找到已配对设备');
+ _connectionStatus.value = ConnectionStatus.disconnected;
+ return;
+ }
+
+ // 遍历配对设备,查找已连接的设备
+ for (var device in pairedDevices) {
+ try {
+ final state = await device.connectionState.first;
+ if (state == BluetoothConnectionState.connected) {
+ // 找到已连接的设备,更新UI
+ final success = await _bluetoothService!.connectToDevice(device);
+ if (success) {
+ Logger.info('使用已连接的已配对设备: ${device.platformName}');
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
+ _connectionStatus.value = ConnectionStatus.connected;
+ _updateConnectionStatus();
+ return;
+ }
+ }
+ } catch (e) {
+ // 忽略单个设备检查错误
+ Logger.error('检查设备连接状态失败: $e');
+ }
+ }
+
+ // 如果没有已连接的设备,尝试连接第一个配对设备
+ if (pairedDevices.isNotEmpty) {
+ Logger.info('尝试连接第一个配对设备: ${pairedDevices.first.platformName}');
+ final success = await _bluetoothService!.connectToDevice(pairedDevices.first);
+ if (success) {
+ Logger.info('自动连接到已配对设备: ${pairedDevices.first.platformName}');
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
+ _connectionStatus.value = ConnectionStatus.connected;
+ _updateConnectionStatus();
+ } else {
+ // 连接失败
+ _connectionStatus.value = ConnectionStatus.failed;
+ _connectionErrorMsg.value = '连接设备失败,请重试';
+ }
+ } else {
+ _connectionStatus.value = ConnectionStatus.disconnected;
+ }
}
- count = (count + 1) % 4;
- _searchDots.value = '.' * count;
- });
+ } catch (e) {
+ Logger.error('检查已配对设备失败: $e');
+ _connectionStatus.value = ConnectionStatus.failed;
+ _connectionErrorMsg.value = '检查设备失败: ${e.toString()}';
+ }
}
-
- void stopSearchingAnimation() {
- _animationTimer?.cancel();
- _animationTimer = null;
- if (!_isDisposed) {
- _searchDots.value = '';
+
+ // 更新连接状态
+ void _updateConnectionStatus() {
+ if (_deviceInfo.containsKey('connected')) {
+ _isConnected.value = _deviceInfo['connected'] ?? false;
+
+ // 如果设备信息显示已连接,但状态不是已连接,则更新状态
+ if (_isConnected.value && _connectionStatus.value != ConnectionStatus.connected) {
+ _connectionStatus.value = ConnectionStatus.connected;
+ } else if (!_isConnected.value && _connectionStatus.value == ConnectionStatus.connected) {
+ // 如果设备显示未连接,但状态是已连接,则更新状态
+ _connectionStatus.value = ConnectionStatus.disconnected;
+ }
+ } else {
+ _isConnected.value = false;
+ if (_connectionStatus.value == ConnectionStatus.connected) {
+ _connectionStatus.value = ConnectionStatus.disconnected;
+ }
}
}
-
- Future connectDevice() async {
- if (_connectStatus.value == ConnectStatus.searching) return;
+
+ // 模拟电池状态更新
+ void _simulateBatteryUpdates() {
+ // 在实际应用中,这里应该从设备获取真实的电池状态
+ // 这里只是模拟随机波动
+ }
+
+ // 探索AI功能
+ void exploreAIFeatures() {
+ Get.toNamed(Routes.explore);
+ }
+
+ // 更新固件
+ void updateFirmware() {
+ Get.snackbar(
+ '提示',
+ '开始下载固件更新...',
+ snackPosition: SnackPosition.TOP,
+ );
- _connectStatus.value = ConnectStatus.searching;
- startSearchingAnimation();
-
+ // 模拟更新过程
+ // 在真实应用中,这里应该有固件更新的实际逻辑
+ }
+
+ // 打开降噪功能
+ void toggleNoiseReduction() {
+ Get.snackbar(
+ '提示',
+ '降噪功能已开启',
+ snackPosition: SnackPosition.TOP,
+ );
+ }
+
+ // 音效设置
+ void openSoundEffects() {
+ Get.snackbar(
+ '提示',
+ '音效设置功能尚未实现',
+ snackPosition: SnackPosition.TOP,
+ );
+ }
+
+ // 环境音功能
+ void toggleAmbientSound() {
+ Get.snackbar(
+ '提示',
+ '环境音模式已开启',
+ snackPosition: SnackPosition.TOP,
+ );
+ }
+
+ // 收藏的设置
+ void openFavorites() {
+ Get.snackbar(
+ '提示',
+ '收藏功能尚未实现',
+ snackPosition: SnackPosition.TOP,
+ );
+ }
+
+ // 打开设置页面
+ void openSettings() {
+ Get.toNamed(Routes.settings);
+ }
+
+ // 重新扫描设备
+ void rescanDevices() {
+ if (_bluetoothService != null) {
+ Get.toNamed(Routes.pairing);
+ } else {
+ Get.snackbar(
+ '提示',
+ '蓝牙服务不可用',
+ snackPosition: SnackPosition.TOP,
+ );
+ }
+ }
+
+ // 获取设备名称
+ String getDeviceName() {
+ if (_deviceInfo.isEmpty || _deviceInfo['device'] == null) {
+ return '未连接设备';
+ }
+
+ final device = _deviceInfo['device'] as Map;
+ return device['name'] ?? '未命名设备';
+ }
+
+ // 获取设备ID
+ String getDeviceId() {
+ if (_deviceInfo.isEmpty || _deviceInfo['device'] == null) {
+ return '';
+ }
+
+ final device = _deviceInfo['device'] as Map;
+ return device['id'] ?? '';
+ }
+
+ // 获取设备信号强度
+ String getSignalStrength() {
+ if (_deviceInfo.isEmpty || _deviceInfo['device'] == null) {
+ return '无信号';
+ }
+
+ final device = _deviceInfo['device'] as Map;
+ final rssi = device['rssi'];
+
+ if (rssi == null) {
+ return '未知';
+ }
+
+ // RSSI通常在-100到0之间,值越大信号越强
+ if (rssi > -60) {
+ return '强';
+ } else if (rssi > -80) {
+ return '中';
+ } else {
+ return '弱';
+ }
+ }
+
+ // 刷新设备信息
+ Future refreshDeviceInfo() async {
try {
- // 模拟搜索和连接过程
- await Future.delayed(const Duration(seconds: 3));
+ // 检查蓝牙是否可用
+ if (_bluetoothService == null) {
+ Get.snackbar(
+ '提示',
+ '蓝牙服务不可用',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red[100],
+ colorText: Colors.red[800],
+ );
+ return;
+ }
- if (_isDisposed) return; // Check if controller is disposed
+ // 设置正在刷新状态
+ _connectionStatus.value = ConnectionStatus.connecting;
- // 模拟随机成功或失败
- if (DateTime.now().millisecondsSinceEpoch % 2 == 0) {
- _connectStatus.value = ConnectStatus.connected;
- if (!_isDisposed) {
- await Future.delayed(const Duration(seconds: 2)); // 显示成功状态2秒
+ if (_bluetoothService!.selectedDevice != null) {
+ // 如果有选中的设备,刷新设备信息
+ _deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
+ _updateConnectionStatus();
+
+ // 检查刷新后的连接状态
+ if (_isConnected.value) {
+ Get.snackbar(
+ '提示',
+ '设备信息已更新',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.green[100],
+ colorText: Colors.green[800],
+ );
+ } else {
+ // 设备可能已断开连接
+ _connectionStatus.value = ConnectionStatus.disconnected;
Get.snackbar(
- 'Success',
- 'Device connected successfully',
+ '提示',
+ '设备已断开连接',
snackPosition: SnackPosition.TOP,
- backgroundColor: Colors.green.withOpacity(0.1),
- colorText: Colors.green,
- duration: const Duration(seconds: 3),
+ backgroundColor: Colors.amber[100],
+ colorText: Colors.amber[800],
);
+
+ // 尝试重新连接
+ await _checkPairedDevices();
}
} else {
- _connectStatus.value = ConnectStatus.failed;
+ // 如果没有选中的设备,尝试连接已配对设备
+ await _checkPairedDevices();
+
+ if (_connectionStatus.value == ConnectionStatus.failed) {
+ Get.snackbar(
+ '提示',
+ _connectionErrorMsg.value.isNotEmpty ? _connectionErrorMsg.value : '连接设备失败',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red[100],
+ colorText: Colors.red[800],
+ );
+ } else if (_connectionStatus.value == ConnectionStatus.disconnected) {
+ Get.snackbar(
+ '提示',
+ '未找到已连接设备,请连接设备',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.amber[100],
+ colorText: Colors.amber[800],
+ );
+ }
}
} catch (e) {
- if (!_isDisposed) {
- _connectStatus.value = ConnectStatus.failed;
- Get.snackbar(
- 'Error',
- 'Failed to connect to device',
- snackPosition: SnackPosition.TOP,
- backgroundColor: Colors.red.withOpacity(0.1),
- colorText: Colors.red,
- duration: const Duration(seconds: 3),
- );
- }
- } finally {
- if (!_isDisposed && _connectStatus.value == ConnectStatus.searching) {
- stopSearchingAnimation();
- _connectStatus.value = ConnectStatus.failed;
- }
+ Logger.error('刷新设备信息失败: $e');
+ _connectionStatus.value = ConnectionStatus.failed;
+ _connectionErrorMsg.value = '刷新设备信息失败: ${e.toString()}';
+
+ Get.snackbar(
+ '提示',
+ '刷新设备信息失败',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red[100],
+ colorText: Colors.red[800],
+ );
}
}
-
- void retryConnect() {
- if (_isDisposed) return;
- _connectStatus.value = ConnectStatus.initial;
- connectDevice();
+
+ // 处理连接失败
+ String getConnectionStatusText() {
+ switch (_connectionStatus.value) {
+ case ConnectionStatus.connected:
+ return "已连接";
+ case ConnectionStatus.connecting:
+ return "连接中...";
+ case ConnectionStatus.disconnected:
+ return "未连接";
+ case ConnectionStatus.failed:
+ return "连接失败";
+ }
}
-
- void disconnectDevice() {
- if (_isDisposed) return;
- _connectStatus.value = ConnectStatus.initial;
- Get.snackbar(
- 'Disconnected',
- 'Device has been disconnected',
- snackPosition: SnackPosition.TOP,
- backgroundColor: Colors.blue.withOpacity(0.1),
- colorText: Colors.blue,
- duration: const Duration(seconds: 3),
- );
+
+ // 获取连接状态颜色
+ Color getConnectionStatusColor() {
+ switch (_connectionStatus.value) {
+ case ConnectionStatus.connected:
+ return Colors.green;
+ case ConnectionStatus.connecting:
+ return Colors.blue;
+ case ConnectionStatus.disconnected:
+ return Colors.red;
+ case ConnectionStatus.failed:
+ return Colors.red;
+ }
}
}
\ No newline at end of file
diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart
index 8b068ceb1..ca6a07e95 100644
--- a/lib/modules/home/views/home_view.dart
+++ b/lib/modules/home/views/home_view.dart
@@ -3,281 +3,685 @@ import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/home_controller.dart';
import '../../../core/widgets/common_bottom_nav.dart';
-import '../../../core/routes/app_routes.dart';
+
+// 导入枚举
+import '../controllers/home_controller.dart' show ConnectionStatus;
class HomeView extends GetView {
const HomeView({Key? key}) : super(key: key);
- Widget _buildStatusWidget() {
- return Obx(() {
- switch (controller.connectStatus) {
- case ConnectStatus.searching:
- return Column(
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.grey[50],
+ body: SafeArea(
+ child: Column(
+ children: [
+ // 状态栏和标题区域
+ _buildHeader(),
+
+ // 主要内容区域(可滚动)
+ Expanded(
+ child: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ child: Padding(
+ padding: EdgeInsets.fromLTRB(16.w, 0, 16.w, 16.h),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // 耳机图片
+ _buildEarbudsImage(),
+
+ // 设备状态卡片
+ SizedBox(height: 16.h),
+ _buildDeviceStatusCard(),
+
+ // AI功能部分
+ SizedBox(height: 16.h),
+ _buildAIFunctionsCard(),
+
+ // 固件更新提示
+ SizedBox(height: 16.h),
+ _buildFirmwareUpdateCard(),
+
+ // 快速功能访问
+ SizedBox(height: 24.h),
+ _buildQuickFunctions(),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ bottomNavigationBar: const CommonBottomNav(currentIndex: 0),
+ );
+ }
+
+ // 页面顶部:状态栏和标题
+ Widget _buildHeader() {
+ return Container(
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
+ child: Column(
+ children: [
+ // 删除状态栏
+
+ // 标题区域
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
- 'searching'.tr + controller.searchDots,
+ 'DeepVoice Pro',
style: TextStyle(
- fontSize: 16.sp,
- color: Colors.grey[600],
+ fontSize: 20.sp,
+ fontWeight: FontWeight.bold,
+ color: Colors.grey[800],
),
),
+ Container(
+ width: 32.w,
+ height: 32.w,
+ decoration: BoxDecoration(
+ color: Colors.grey[200],
+ shape: BoxShape.circle,
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.2),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: IconButton(
+ icon: Icon(Icons.more_horiz, size: 16.sp, color: Colors.grey[600]),
+ padding: EdgeInsets.zero,
+ onPressed: () {
+ // 打开更多选项菜单
+ },
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+
+ // 耳机图片
+ Widget _buildEarbudsImage() {
+ return Container(
+ height: 180.h,
+ alignment: Alignment.center,
+ child: Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.2),
+ blurRadius: 10,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(16.r),
+ child: Container(
+ color: Colors.white,
+ padding: EdgeInsets.all(16.w),
+ child: Icon(
+ Icons.headset,
+ size: 120.sp,
+ color: Colors.blueGrey,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ // 设备状态卡片
+ Widget _buildDeviceStatusCard() {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 10,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ padding: EdgeInsets.all(16.w),
+ child: Column(
+ children: [
+ // 状态标题行
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
Text(
- 'searching_hint'.tr,
+ '设备状态',
style: TextStyle(
fontSize: 14.sp,
- color: Colors.grey[400],
- height: 2,
+ fontWeight: FontWeight.w500,
+ color: Colors.grey[700],
),
),
- ],
- );
- case ConnectStatus.connected:
- return Column(
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
+ Obx(() => Row(
children: [
- Icon(
- Icons.check_circle,
- color: Colors.green,
- size: 20.sp,
+ Container(
+ width: 8.w,
+ height: 8.w,
+ decoration: BoxDecoration(
+ color: controller.getConnectionStatusColor(),
+ shape: BoxShape.circle,
+ ),
),
- SizedBox(width: 8.w),
+ SizedBox(width: 4.w),
Text(
- 'connected'.tr,
+ controller.getConnectionStatusText(),
style: TextStyle(
- fontSize: 16.sp,
- color: Colors.green,
- fontWeight: FontWeight.w500,
+ fontSize: 12.sp,
+ color: controller.getConnectionStatusColor(),
+ ),
+ ),
+ // 添加刷新按钮
+ SizedBox(width: 8.w),
+ InkWell(
+ onTap: controller.refreshDeviceInfo,
+ child: Icon(
+ Icons.refresh,
+ size: 16.sp,
+ color: Colors.grey[600],
),
),
],
- ),
- Text(
- 'device_ready'.tr,
- style: TextStyle(
- fontSize: 14.sp,
- color: Colors.grey[400],
- height: 2,
- ),
- ),
+ )),
],
- );
- case ConnectStatus.failed:
- return Column(
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
+ ),
+
+ // 设备信息
+ SizedBox(height: 12.h),
+ Obx(() {
+ // 如果连接失败且有错误信息,显示错误提示
+ if (controller.connectionStatus == ConnectionStatus.failed &&
+ controller.connectionErrorMsg.isNotEmpty) {
+ return Column(
children: [
- Icon(
- Icons.error,
- color: Colors.red[400],
- size: 20.sp,
+ Container(
+ padding: EdgeInsets.all(8.w),
+ decoration: BoxDecoration(
+ color: Colors.red[50],
+ borderRadius: BorderRadius.circular(8.r),
+ border: Border.all(color: Colors.red[200]!),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.error_outline,
+ color: Colors.red,
+ size: 16.sp,
+ ),
+ SizedBox(width: 8.w),
+ Expanded(
+ child: Text(
+ controller.connectionErrorMsg,
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.red[800],
+ ),
+ ),
+ ),
+ ],
+ ),
),
- SizedBox(width: 8.w),
- Text(
- 'connection_failed'.tr,
- style: TextStyle(
- fontSize: 16.sp,
- color: Colors.red[400],
- fontWeight: FontWeight.w500,
+ SizedBox(height: 12.h),
+ // 连接按钮
+ ElevatedButton(
+ onPressed: controller.rescanDevices,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.blue,
+ foregroundColor: Colors.white,
+ minimumSize: Size(double.infinity, 36.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.r),
+ ),
),
+ child: Text('重新连接'),
),
],
- ),
- SizedBox(height: 12.h),
+ );
+ }
+
+ // 如果正在连接中,显示连接中提示
+ if (controller.connectionStatus == ConnectionStatus.connecting) {
+ return Container(
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ child: Center(
+ child: Column(
+ children: [
+ SizedBox(
+ width: 24.w,
+ height: 24.w,
+ child: CircularProgressIndicator(
+ strokeWidth: 2.w,
+ valueColor: AlwaysStoppedAnimation(Colors.blue),
+ ),
+ ),
+ SizedBox(height: 8.h),
+ Text(
+ '正在连接设备...',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.blue,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ return controller.isConnected
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Text(
+ '已连接到:${controller.getDeviceName()}',
+ style: TextStyle(
+ fontSize: 12.sp,
+ fontWeight: FontWeight.w500,
+ color: Colors.grey[700],
+ ),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
+ decoration: BoxDecoration(
+ color: Colors.blue.withOpacity(0.1),
+ borderRadius: BorderRadius.circular(4.r),
+ ),
+ child: Text(
+ '信号: ${controller.getSignalStrength()}',
+ style: TextStyle(
+ fontSize: 10.sp,
+ color: Colors.blue[700],
+ ),
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
+ decoration: BoxDecoration(
+ color: Colors.grey[50],
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '设备ID:${controller.getDeviceId()}',
+ style: TextStyle(
+ fontSize: 10.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Text(
+ '固件版本:${controller.firmwareVersion}',
+ style: TextStyle(
+ fontSize: 10.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ )
+ : Container(
+ padding: EdgeInsets.symmetric(vertical: 8.h),
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.bluetooth_disabled,
+ size: 14.sp,
+ color: Colors.grey[700],
+ ),
+ SizedBox(width: 4.w),
+ Text(
+ '未连接设备,点击下方按钮连接',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[700],
+ ),
+ ),
+ ],
+ ),
+ );
+ }),
+
+ // 连接按钮(仅在未连接时显示)
+ SizedBox(height: 12.h),
+ Obx(() {
+ if (controller.connectionStatus == ConnectionStatus.disconnected) {
+ return ElevatedButton(
+ onPressed: controller.rescanDevices,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.blue,
+ foregroundColor: Colors.white,
+ minimumSize: Size(double.infinity, 36.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ ),
+ child: Text('连接设备'),
+ );
+ } else {
+ return SizedBox();
+ }
+ }),
+
+ // 电池状态
+ SizedBox(height: 12.h),
+ Obx(() => controller.isConnected
+ ? Row(
+ children: [
+ // 左耳机
+ Expanded(
+ child: _buildBatteryItem('左耳机', controller.leftEarbudBattery, Icons.headset),
+ ),
+ SizedBox(width: 12.w),
+ // 右耳机
+ Expanded(
+ child: _buildBatteryItem('右耳机', controller.rightEarbudBattery, Icons.headset),
+ ),
+ SizedBox(width: 12.w),
+ // 充电盒
+ Expanded(
+ child: _buildBatteryItem('充电盒', controller.caseBattery, Icons.cases_outlined),
+ ),
+ ],
+ )
+ : const SizedBox()),
+ ],
+ ),
+ );
+ }
+
+ // 电池状态项
+ Widget _buildBatteryItem(String label, int percentage, IconData icon) {
+ Color batteryColor;
+ IconData batteryIcon;
+
+ // 根据电量设置颜色和图标
+ if (percentage >= 70) {
+ batteryColor = Colors.green;
+ batteryIcon = Icons.battery_full;
+ } else if (percentage >= 30) {
+ batteryColor = Colors.amber;
+ batteryIcon = Icons.battery_5_bar;
+ } else {
+ batteryColor = Colors.red;
+ batteryIcon = Icons.battery_1_bar;
+ }
+
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(12.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ padding: EdgeInsets.all(12.w),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(icon, size: 24.sp, color: Colors.grey[700]),
+ SizedBox(height: 8.h),
+ Text(
+ label,
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(batteryIcon, size: 14.sp, color: batteryColor),
+ SizedBox(width: 4.w),
Text(
- 'connection_failed_hint'.tr,
- textAlign: TextAlign.center,
+ '$percentage%',
style: TextStyle(
- fontSize: 14.sp,
- color: Colors.grey[500],
- height: 1.8,
+ fontSize: 12.sp,
+ fontWeight: FontWeight.w500,
+ color: Colors.grey[700],
),
),
],
- );
- default:
- return const SizedBox.shrink();
- }
- });
+ ),
+ ],
+ ),
+ );
}
- Widget _buildActionButton() {
- return Obx(() {
- switch (controller.connectStatus) {
- case ConnectStatus.searching:
- return Container(
- width: double.infinity,
- height: 56.h,
- margin: EdgeInsets.symmetric(horizontal: 20.w),
- decoration: BoxDecoration(
- color: Colors.grey[100],
- borderRadius: BorderRadius.circular(28.r),
- ),
- child: Center(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SizedBox(
- width: 20.w,
- height: 20.w,
- child: const CircularProgressIndicator(
- strokeWidth: 2,
- valueColor: AlwaysStoppedAnimation(Color(0xFF4A6FE5)),
- ),
- ),
- SizedBox(width: 12.w),
- Text(
- 'searching'.tr,
- style: TextStyle(
- fontSize: 18.sp,
- fontWeight: FontWeight.w600,
- color: Colors.grey[400],
- ),
- ),
- ],
+ // AI功能卡片
+ Widget _buildAIFunctionsCard() {
+ return Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ Colors.blue.shade100,
+ Colors.purple.shade100,
+ ],
+ ),
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 10,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ padding: EdgeInsets.all(16.w),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'AI助手',
+ style: TextStyle(
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w500,
+ color: Colors.grey[800],
+ ),
),
- ),
- );
- case ConnectStatus.failed:
- return TextButton(
- onPressed: controller.retryConnect,
- style: TextButton.styleFrom(
- padding: EdgeInsets.symmetric(horizontal: 40.w, vertical: 12.h),
- backgroundColor: Colors.blue[50],
+ SizedBox(height: 4.h),
+ Text(
+ '探索DeepVoice的智能功能',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[600],
+ ),
+ ),
+ ],
+ ),
+ ElevatedButton(
+ onPressed: controller.exploreAIFeatures,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.blue[600],
+ foregroundColor: Colors.white,
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(28.r),
+ borderRadius: BorderRadius.circular(24.r),
),
+ elevation: 2,
),
child: Text(
- 'retry'.tr,
+ '探索',
style: TextStyle(
- fontSize: 18.sp,
- color: const Color(0xFF4A6FE5),
- fontWeight: FontWeight.w600,
+ fontSize: 14.sp,
+ fontWeight: FontWeight.w500,
),
),
- );
- case ConnectStatus.connected:
- return Container(
- width: double.infinity,
- height: 56.h,
- margin: EdgeInsets.symmetric(horizontal: 20.w),
- decoration: BoxDecoration(
- gradient: const LinearGradient(
- colors: [Color(0xFF4CAF50), Color(0xFF66BB6A)],
- begin: Alignment.centerLeft,
- end: Alignment.centerRight,
- ),
- borderRadius: BorderRadius.circular(28.r),
- boxShadow: [
- BoxShadow(
- color: const Color(0xFF4CAF50).withOpacity(0.3),
- offset: const Offset(0, 4),
- blurRadius: 12,
+ ),
+ ],
+ ),
+ );
+ }
+
+ // 固件更新卡片
+ Widget _buildFirmwareUpdateCard() {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 10,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ padding: EdgeInsets.all(16.w),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ children: [
+ Container(
+ width: 40.w,
+ height: 40.w,
+ decoration: BoxDecoration(
+ color: Colors.amber[100],
+ shape: BoxShape.circle,
),
- ],
- ),
- child: Material(
- color: Colors.transparent,
- child: InkWell(
- onTap: controller.disconnectDevice,
- borderRadius: BorderRadius.circular(28.r),
- child: Center(
- child: Text(
- 'disconnect'.tr,
+ child: Icon(
+ Icons.download,
+ size: 20.sp,
+ color: Colors.amber[600],
+ ),
+ ),
+ SizedBox(width: 12.w),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '固件更新',
style: TextStyle(
- fontSize: 18.sp,
- fontWeight: FontWeight.w600,
- color: Colors.white,
- letterSpacing: 0.5,
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w500,
+ color: Colors.grey[800],
),
),
- ),
- ),
- ),
- );
- default:
- return Container(
- width: double.infinity,
- height: 56.h,
- margin: EdgeInsets.symmetric(horizontal: 20.w),
- decoration: BoxDecoration(
- gradient: const LinearGradient(
- colors: [Color(0xFF4A6FE5), Color(0xFF6B8FF7)],
- begin: Alignment.centerLeft,
- end: Alignment.centerRight,
- ),
- borderRadius: BorderRadius.circular(28.r),
- boxShadow: [
- BoxShadow(
- color: const Color(0xFF4A6FE5).withOpacity(0.3),
- offset: const Offset(0, 4),
- blurRadius: 12,
- ),
- ],
- ),
- child: Material(
- color: Colors.transparent,
- child: InkWell(
- onTap: controller.connectDevice,
- borderRadius: BorderRadius.circular(28.r),
- child: Center(
- child: Text(
- 'add_device'.tr,
+ Text(
+ '${controller.firmwareVersion}可用',
style: TextStyle(
- fontSize: 18.sp,
- fontWeight: FontWeight.w600,
- color: Colors.white,
- letterSpacing: 0.5,
+ fontSize: 12.sp,
+ color: Colors.grey[500],
),
),
- ),
+ ],
+ ),
+ ],
+ ),
+ ElevatedButton(
+ onPressed: controller.updateFirmware,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.grey[200],
+ foregroundColor: Colors.grey[700],
+ padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(24.r),
),
+ elevation: 0,
),
- );
- }
- });
+ child: Text(
+ '更新',
+ style: TextStyle(
+ fontSize: 12.sp,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
}
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Colors.white,
- body: SafeArea(
- child: Container(
- padding: EdgeInsets.symmetric(horizontal: 24.w),
- child: Column(
- children: [
- SizedBox(height: 40.h),
- Text(
- 'app_name'.tr,
- style: TextStyle(
- fontSize: 24.sp,
- fontWeight: FontWeight.bold,
- color: Colors.black87,
- ),
- ),
- SizedBox(height: 40.h),
- Image.asset(
- 'assets/images/headphone.png',
- width: 200.w,
- height: 200.w,
- fit: BoxFit.contain,
- ),
- Expanded(
- child: Center(
- child: _buildStatusWidget(),
- ),
+ // 快速功能访问
+ Widget _buildQuickFunctions() {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ _buildQuickFunctionItem('降噪', Icons.volume_off, Colors.blue[600]!, Colors.blue[100]!, controller.toggleNoiseReduction),
+ _buildQuickFunctionItem('音效', Icons.music_note, Colors.purple[600]!, Colors.purple[100]!, controller.openSoundEffects),
+ _buildQuickFunctionItem('环境音', Icons.eco, Colors.green[600]!, Colors.green[100]!, controller.toggleAmbientSound),
+ _buildQuickFunctionItem('收藏', Icons.favorite, Colors.red[600]!, Colors.red[100]!, controller.openFavorites),
+ ],
+ );
+ }
+
+ // 快速功能项
+ Widget _buildQuickFunctionItem(String label, IconData icon, Color iconColor, Color bgColor, VoidCallback onTap) {
+ return Column(
+ children: [
+ Container(
+ width: 48.w,
+ height: 48.w,
+ decoration: BoxDecoration(
+ color: bgColor,
+ shape: BoxShape.circle,
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.2),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
),
- _buildActionButton(),
- SizedBox(height: 40.h),
],
),
+ child: IconButton(
+ icon: Icon(icon, color: iconColor),
+ onPressed: onTap,
+ ),
),
- ),
- bottomNavigationBar: const CommonBottomNav(currentIndex: 0),
+ SizedBox(height: 4.h),
+ Text(
+ label,
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[700],
+ ),
+ ),
+ ],
);
}
}
\ No newline at end of file
diff --git a/lib/modules/login/bindings/login_binding.dart b/lib/modules/login/bindings/login_binding.dart
new file mode 100644
index 000000000..6729681f9
--- /dev/null
+++ b/lib/modules/login/bindings/login_binding.dart
@@ -0,0 +1,9 @@
+import 'package:get/get.dart';
+import '../controllers/login_controller.dart';
+
+class LoginBinding extends Bindings {
+ @override
+ void dependencies() {
+ Get.lazyPut(() => LoginController());
+ }
+}
\ No newline at end of file
diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart
new file mode 100644
index 000000000..c72d5f623
--- /dev/null
+++ b/lib/modules/login/controllers/login_controller.dart
@@ -0,0 +1,246 @@
+import 'package:get/get.dart';
+import 'package:flutter/material.dart';
+import '../../../core/routes/app_routes.dart';
+
+enum LoginViewState {
+ welcome, // 欢迎页面
+ verification // 验证码页面
+}
+
+class LoginController extends GetxController {
+ static LoginController get to => Get.find();
+
+ // 状态管理
+ final _viewState = LoginViewState.welcome.obs;
+ final _isLoading = false.obs;
+ final _countdownSeconds = 0.obs;
+ final _contactValue = ''.obs;
+ final _displayContact = ''.obs;
+ final _verificationCode = ['', '', '', ''].obs;
+
+ // Getters
+ LoginViewState get viewState => _viewState.value;
+ bool get isLoading => _isLoading.value;
+ int get countdownSeconds => _countdownSeconds.value;
+ String get contactValue => _contactValue.value;
+ String get displayContact => _displayContact.value;
+ List get verificationCode => _verificationCode;
+
+ // 文本控制器
+ final contactController = TextEditingController();
+ final verificationCodeController = TextEditingController();
+
+ @override
+ void onInit() {
+ super.onInit();
+ _viewState.value = LoginViewState.welcome;
+ }
+
+ @override
+ void onClose() {
+ contactController.dispose();
+ verificationCodeController.dispose();
+ super.onClose();
+ }
+
+ // 获取验证码
+ void getVerificationCode() {
+ if (contactController.text.isEmpty) {
+ Get.snackbar(
+ '提示',
+ '请输入手机号码或邮箱',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red.withOpacity(0.1),
+ colorText: Colors.red,
+ );
+ return;
+ }
+
+ _contactValue.value = contactController.text;
+ _formatDisplayContact();
+ _startCountdown();
+ _sendVerificationCode();
+ }
+
+ // 使用验证码直接登录
+ void loginWithVerificationCode() {
+ //测试期间暂时关闭验证码登录
+ // if (contactController.text.isEmpty) {
+ // Get.snackbar(
+ // '提示',
+ // '请输入手机号码或邮箱',
+ // snackPosition: SnackPosition.TOP,
+ // backgroundColor: Colors.red.withOpacity(0.1),
+ // colorText: Colors.red,
+ // );
+ // return;
+ // }
+
+ // if (verificationCodeController.text.isEmpty) {
+ // Get.snackbar(
+ // '提示',
+ // '请输入验证码',
+ // snackPosition: SnackPosition.TOP,
+ // backgroundColor: Colors.red.withOpacity(0.1),
+ // colorText: Colors.red,
+ // );
+ // return;
+ // }
+
+ // if (verificationCodeController.text.length < 6) {
+ // Get.snackbar(
+ // '提示',
+ // '请输入完整的验证码',
+ // snackPosition: SnackPosition.TOP,
+ // backgroundColor: Colors.red.withOpacity(0.1),
+ // colorText: Colors.red,
+ // );
+ // return;
+ // }
+
+ _isLoading.value = true;
+
+ // 模拟验证过程
+ Future.delayed(const Duration(seconds: 2), () {
+ _isLoading.value = false;
+ // 验证成功,跳转到配对页面
+ Get.offAllNamed(Routes.pairing);
+ });
+ }
+
+ // 切换到验证码页面
+ void showVerificationView() {
+ if (contactController.text.isEmpty) {
+ Get.snackbar(
+ '提示',
+ '请输入手机号码或邮箱',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red.withOpacity(0.1),
+ colorText: Colors.red,
+ );
+ return;
+ }
+
+ _contactValue.value = contactController.text;
+ _formatDisplayContact();
+ _viewState.value = LoginViewState.verification;
+ _startCountdown();
+ _sendVerificationCode();
+ }
+
+ // 返回欢迎页面
+ void showWelcomeView() {
+ _viewState.value = LoginViewState.welcome;
+ _resetVerificationCode();
+ }
+
+ // 格式化显示的联系方式(隐藏部分字符)
+ void _formatDisplayContact() {
+ String contact = _contactValue.value;
+ if (contact.contains('@')) { // 邮箱
+ final parts = contact.split('@');
+ if (parts[0].length > 3) {
+ _displayContact.value = parts[0].substring(0, 3) + '****@' + parts[1];
+ } else {
+ _displayContact.value = contact;
+ }
+ } else if (RegExp(r'^\d+$').hasMatch(contact)) { // 手机号
+ if (contact.length > 7) {
+ _displayContact.value = '+86 ' + contact.substring(0, 3) + '****' + contact.substring(contact.length - 4);
+ } else {
+ _displayContact.value = contact;
+ }
+ } else {
+ _displayContact.value = contact;
+ }
+ }
+
+ // 开始倒计时
+ void _startCountdown() {
+ _countdownSeconds.value = 60;
+ Future.doWhile(() async {
+ await Future.delayed(const Duration(seconds: 1));
+ _countdownSeconds.value--;
+ return _countdownSeconds.value > 0;
+ });
+ }
+
+ // 发送验证码
+ void _sendVerificationCode() {
+ // 这里应该是实际发送验证码的逻辑
+ // 为了演示,我们只是模拟发送
+ }
+
+ // 重新发送验证码
+ void resendVerificationCode() {
+ if (_countdownSeconds.value > 0) return;
+ _startCountdown();
+ _sendVerificationCode();
+ }
+
+ // 更新验证码输入
+ void updateVerificationCode(int index, String value) {
+ if (index >= 0 && index < 4) {
+ var newList = [..._verificationCode];
+ newList[index] = value;
+ _verificationCode.value = newList;
+ }
+ }
+
+ // 重置验证码
+ void _resetVerificationCode() {
+ _verificationCode.value = ['', '', '', ''];
+ }
+
+ // 检查验证码是否完整
+ bool isVerificationCodeComplete() {
+ return !_verificationCode.contains('');
+ }
+
+ // 登录
+ Future login() async {
+ // 暂时关闭验证码验证
+ if (!isVerificationCodeComplete()) {
+ Get.snackbar(
+ '提示',
+ '请输入完整的验证码',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red.withOpacity(0.1),
+ colorText: Colors.red,
+ );
+ return;
+ }
+
+ _isLoading.value = true;
+
+ try {
+ // 模拟验证过程
+ await Future.delayed(const Duration(seconds: 2));
+
+ // 验证成功,跳转到配对页面
+ Get.offAllNamed(Routes.pairing);
+ } catch (e) {
+ Get.snackbar(
+ '错误',
+ '登录失败,请重试',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red.withOpacity(0.1),
+ colorText: Colors.red,
+ );
+ } finally {
+ _isLoading.value = false;
+ }
+ }
+
+ // 使用密码登录
+ void usePasswordLogin() {
+ // 这里可以实现密码登录的逻辑
+ Get.snackbar(
+ '提示',
+ '密码登录功能尚未实现',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.blue.withOpacity(0.1),
+ colorText: Colors.blue,
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart
new file mode 100644
index 000000000..931a243dd
--- /dev/null
+++ b/lib/modules/login/views/login_view.dart
@@ -0,0 +1,628 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:font_awesome_flutter/font_awesome_flutter.dart';
+import '../controllers/login_controller.dart';
+
+class LoginView extends GetView {
+ const LoginView({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.grey[50],
+ body: SafeArea(
+ child: Obx(() => controller.viewState == LoginViewState.welcome
+ ? _buildWelcomeView()
+ : _buildVerificationView(context)),
+ ),
+ );
+ }
+
+ // 欢迎页面
+ Widget _buildWelcomeView() {
+ return Padding(
+ padding: EdgeInsets.all(24.w),
+ child: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ child: Column(
+ children: [
+ // 状态栏
+ _buildStatusBar(),
+
+ // 应用标志和标题
+ SizedBox(height: 32.h),
+ _buildAppLogo(),
+
+ // 欢迎文本
+ SizedBox(height: 32.h),
+ _buildWelcomeText(),
+
+ // 输入框和继续按钮
+ SizedBox(height: 24.h),
+ _buildInputField(),
+ SizedBox(height: 16.h),
+ _buildVerificationInputField(),
+ SizedBox(height: 16.h),
+ _buildContinueButton(),
+
+ // 社交媒体登录
+ SizedBox(height: 32.h),
+ _buildSocialLoginSection(),
+
+ // 用户协议
+ SizedBox(height: 40.h),
+ _buildTermsAndPrivacy(),
+ SizedBox(height: 24.h),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // 验证码页面
+ Widget _buildVerificationView(BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.all(24.w),
+ child: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // 返回按钮
+ _buildBackButton(),
+
+ // 验证码标题
+ SizedBox(height: 24.h),
+ _buildVerificationTitle(),
+
+ // 验证码输入框
+ SizedBox(height: 32.h),
+ _buildVerificationInputs(context),
+
+ // 重新发送验证码
+ SizedBox(height: 16.h),
+ _buildResendButton(),
+
+ // 登录按钮
+ SizedBox(height: 32.h),
+ _buildLoginButton(),
+
+ // 使用密码登录
+ SizedBox(height: 16.h),
+ _buildUsePasswordButton(),
+ SizedBox(height: 24.h),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // 状态栏
+ Widget _buildStatusBar() {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ '9:41',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[800],
+ ),
+ ),
+ Row(
+ children: [
+ Icon(Icons.signal_cellular_4_bar, size: 12.sp, color: Colors.grey[800]),
+ SizedBox(width: 4.w),
+ Icon(Icons.wifi, size: 12.sp, color: Colors.grey[800]),
+ SizedBox(width: 4.w),
+ Icon(Icons.battery_full, size: 12.sp, color: Colors.grey[800]),
+ ],
+ ),
+ ],
+ );
+ }
+
+ // 应用标志和标题
+ Widget _buildAppLogo() {
+ return Column(
+ children: [
+ Container(
+ width: 80.w,
+ height: 80.w,
+ decoration: BoxDecoration(
+ gradient: const LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [Colors.blue, Colors.purple],
+ ),
+ borderRadius: BorderRadius.circular(24.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.blue.withOpacity(0.3),
+ blurRadius: 10,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ child: Icon(
+ Icons.headset,
+ size: 40.sp,
+ color: Colors.white,
+ ),
+ ),
+ SizedBox(height: 16.h),
+ Text(
+ 'DeepVoice',
+ style: TextStyle(
+ fontSize: 24.sp,
+ fontWeight: FontWeight.bold,
+ color: Colors.grey[800],
+ ),
+ ),
+ SizedBox(height: 8.h),
+ Text(
+ '连接世界,超越声音',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ ],
+ );
+ }
+
+ // 欢迎文本
+ Widget _buildWelcomeText() {
+ return Column(
+ children: [
+ Text(
+ '欢迎使用',
+ style: TextStyle(
+ fontSize: 20.sp,
+ fontWeight: FontWeight.w600,
+ color: Colors.grey[800],
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Text(
+ '请使用手机号或邮箱登录/注册',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ ],
+ );
+ }
+
+ // 输入框
+ Widget _buildInputField() {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
+ child: Row(
+ children: [
+ Icon(
+ Icons.person_outline,
+ color: Colors.grey[400],
+ size: 20.sp,
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: TextField(
+ controller: controller.contactController,
+ decoration: InputDecoration(
+ hintText: '手机号码或邮箱',
+ hintStyle: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[400],
+ ),
+ border: InputBorder.none,
+ ),
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[700],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // 验证码输入框
+ Widget _buildVerificationInputField() {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(16.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: Padding(
+ padding: EdgeInsets.only(left: 16.w, right: 4.w, top: 4.h, bottom: 4.h),
+ child: Row(
+ children: [
+ Icon(
+ Icons.lock_outline,
+ color: Colors.grey[400],
+ size: 20.sp,
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: TextField(
+ controller: controller.verificationCodeController,
+ keyboardType: TextInputType.number,
+ maxLength: 6,
+ decoration: InputDecoration(
+ hintText: '验证码',
+ hintStyle: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[400],
+ ),
+ border: InputBorder.none,
+ counterText: '',
+ ),
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[700],
+ ),
+ ),
+ ),
+ Obx(() => TextButton(
+ onPressed: controller.countdownSeconds > 0
+ ? null
+ : controller.getVerificationCode,
+ style: TextButton.styleFrom(
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ foregroundColor: Colors.blue[600],
+ backgroundColor: Colors.transparent,
+ disabledForegroundColor: Colors.grey[400],
+ ),
+ child: Text(
+ controller.countdownSeconds > 0
+ ? '${controller.countdownSeconds}s后重新获取'
+ : '获取验证码',
+ style: TextStyle(
+ fontSize: 14.sp,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ )),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // 继续按钮
+ Widget _buildContinueButton() {
+ return SizedBox(
+ width: double.infinity,
+ child: Obx(() => ElevatedButton(
+ onPressed: controller.isLoading ? null : controller.loginWithVerificationCode,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.blue[600],
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(16.r),
+ ),
+ elevation: 2,
+ ),
+ child: controller.isLoading
+ ? SizedBox(
+ width: 20.w,
+ height: 20.w,
+ child: const CircularProgressIndicator(
+ strokeWidth: 2,
+ valueColor: AlwaysStoppedAnimation(Colors.white),
+ ),
+ )
+ : Text(
+ '登录',
+ style: TextStyle(
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w500,
+ color: Colors.white,
+ ),
+ ),
+ )),
+ );
+ }
+
+ // 社交媒体登录部分
+ Widget _buildSocialLoginSection() {
+ return Column(
+ children: [
+ Row(
+ children: [
+ Expanded(child: Divider(color: Colors.grey[300])),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ child: Text(
+ '或使用以下方式登录',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ ),
+ Expanded(child: Divider(color: Colors.grey[300])),
+ ],
+ ),
+ SizedBox(height: 24.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ _buildSocialButton(FontAwesomeIcons.google, Colors.grey[700]!),
+ SizedBox(width: 24.w),
+ _buildSocialButton(FontAwesomeIcons.weixin, Colors.green),
+ ],
+ ),
+ ],
+ );
+ }
+
+ // 社交媒体按钮
+ Widget _buildSocialButton(IconData icon, Color color) {
+ return Container(
+ width: 48.w,
+ height: 48.w,
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(24.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: IconButton(
+ icon: Icon(icon, color: color, size: 20.sp),
+ onPressed: () {},
+ ),
+ );
+ }
+
+ // 用户协议和隐私政策
+ Widget _buildTermsAndPrivacy() {
+ return Column(
+ children: [
+ Text(
+ '登录/注册即表示你同意',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ GestureDetector(
+ onTap: () {},
+ child: Text(
+ '《用户协议》',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.blue[600],
+ ),
+ ),
+ ),
+ Text(
+ ' 和 ',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.grey[500],
+ ),
+ ),
+ GestureDetector(
+ onTap: () {},
+ child: Text(
+ '《隐私政策》',
+ style: TextStyle(
+ fontSize: 12.sp,
+ color: Colors.blue[600],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+
+ // 返回按钮
+ Widget _buildBackButton() {
+ return Container(
+ width: 40.w,
+ height: 40.w,
+ decoration: BoxDecoration(
+ color: Colors.grey[100],
+ borderRadius: BorderRadius.circular(20.r),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.1),
+ blurRadius: 4,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ child: IconButton(
+ icon: Icon(Icons.arrow_back, color: Colors.grey[500], size: 20.sp),
+ onPressed: controller.showWelcomeView,
+ ),
+ );
+ }
+
+ // 验证码标题
+ Widget _buildVerificationTitle() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '输入验证码',
+ style: TextStyle(
+ fontSize: 20.sp,
+ fontWeight: FontWeight.w600,
+ color: Colors.grey[800],
+ ),
+ ),
+ SizedBox(height: 8.h),
+ Obx(() => Text(
+ '验证码已发送至 ${controller.displayContact}',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.grey[500],
+ ),
+ )),
+ ],
+ );
+ }
+
+ // 验证码输入框
+ Widget _buildVerificationInputs(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: List.generate(4, (index) => _buildVerificationInput(index, context)),
+ );
+ }
+
+ // 单个验证码输入框
+ Widget _buildVerificationInput(int index, BuildContext context) {
+ return Container(
+ width: 60.w,
+ height: 60.w,
+ decoration: BoxDecoration(
+ color: Colors.grey[100]!.withOpacity(0.5),
+ borderRadius: BorderRadius.circular(16.r),
+ border: Border.all(
+ color: Colors.grey[300]!,
+ width: 1,
+ ),
+ ),
+ child: Center(
+ child: TextField(
+ textAlign: TextAlign.center,
+ keyboardType: TextInputType.number,
+ maxLength: 1,
+ onChanged: (value) {
+ controller.updateVerificationCode(index, value);
+ if (value.isNotEmpty && index < 3) {
+ FocusScope.of(context).nextFocus();
+ }
+ },
+ decoration: const InputDecoration(
+ counterText: '',
+ border: InputBorder.none,
+ ),
+ style: TextStyle(
+ fontSize: 24.sp,
+ fontWeight: FontWeight.w600,
+ color: Colors.grey[800],
+ ),
+ ),
+ ),
+ );
+ }
+
+ // 重新发送验证码按钮
+ Widget _buildResendButton() {
+ return Center(
+ child: Obx(() => TextButton.icon(
+ onPressed: controller.countdownSeconds > 0
+ ? null
+ : controller.resendVerificationCode,
+ icon: Icon(
+ Icons.refresh,
+ size: 14.sp,
+ color: controller.countdownSeconds > 0
+ ? Colors.grey[400]
+ : Colors.blue[600],
+ ),
+ label: Text(
+ controller.countdownSeconds > 0
+ ? '重新发送验证码 (${controller.countdownSeconds}s)'
+ : '重新发送验证码',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: controller.countdownSeconds > 0
+ ? Colors.grey[400]
+ : Colors.blue[600],
+ ),
+ ),
+ )),
+ );
+ }
+
+ // 登录按钮
+ Widget _buildLoginButton() {
+ return SizedBox(
+ width: double.infinity,
+ child: Obx(() => ElevatedButton(
+ onPressed: controller.isLoading ? null : controller.login,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.blue[600],
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(16.r),
+ ),
+ elevation: 2,
+ ),
+ child: controller.isLoading
+ ? SizedBox(
+ width: 20.w,
+ height: 20.w,
+ child: const CircularProgressIndicator(
+ strokeWidth: 2,
+ valueColor: AlwaysStoppedAnimation(Colors.white),
+ ),
+ )
+ : Text(
+ '登录',
+ style: TextStyle(
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w500,
+ color: Colors.white,
+ ),
+ ),
+ )),
+ );
+ }
+
+ // 使用密码登录按钮
+ Widget _buildUsePasswordButton() {
+ return Center(
+ child: TextButton(
+ onPressed: controller.usePasswordLogin,
+ child: Text(
+ '使用密码登录',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.blue[600],
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/modules/pairing/bindings/pairing_binding.dart b/lib/modules/pairing/bindings/pairing_binding.dart
new file mode 100644
index 000000000..cd57e4ac2
--- /dev/null
+++ b/lib/modules/pairing/bindings/pairing_binding.dart
@@ -0,0 +1,9 @@
+import 'package:get/get.dart';
+import '../controllers/pairing_controller.dart';
+
+class PairingBinding extends Bindings {
+ @override
+ void dependencies() {
+ Get.put(PairingController());
+ }
+}
\ No newline at end of file
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
new file mode 100644
index 000000000..c4fce6a9e
--- /dev/null
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -0,0 +1,295 @@
+import 'dart:async';
+import 'package:get/get.dart';
+import '../../../core/routes/app_routes.dart';
+import '../../../data/services/bluetooth_service.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService;
+import '../../../core/utils/logger.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:flutter/material.dart';
+
+enum SearchStatus {
+ searching, // 正在搜索设备
+ found, // 找到设备
+ failed, // 搜索失败
+ noPermission, // 缺少权限
+}
+
+class PairingController extends GetxController {
+ // 搜索状态
+ final _searchStatus = SearchStatus.searching.obs;
+ SearchStatus get searchStatus => _searchStatus.value;
+
+ // 搜索动画的点
+ final _searchDots = ''.obs;
+ String get searchDots => _searchDots.value;
+
+ // 动画定时器
+ Timer? _animationTimer;
+
+ // 蓝牙服务
+ late BluetoothService _bluetoothService;
+
+ // 设备信息
+ final _deviceInfo = Rx