diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 05d554d39..f3ad0d225 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -59,6 +59,8 @@
193d0a2b1757c41cfa557ea8e8f21c33
LSApplicationQueriesSchemes
+ App-Prefs
+ prefs
weixin
weixinULAPI
weixinURLParamsAPI
@@ -119,6 +121,10 @@
audio
location
+ UIRequiredDeviceCapabilities
+
+ telephony
+
UIDeviceFamily
1
diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart
index 73826786b..52117ff40 100644
--- a/lib/core/bindings/initial_binding.dart
+++ b/lib/core/bindings/initial_binding.dart
@@ -6,6 +6,7 @@ import '../../../data/services/navigation_manager.dart';
import 'package:get/get.dart';
import '../../data/services/meeting/meeting_task_service.dart';
import '../../data/services/meeting/meeting_upload_service.dart';
+import '../../data/services/novice_guide_service.dart';
import '../../data/services/speech_factory.dart';
import '../../data/services/volcano_translation_service.dart';
import '../../data/services/volcano_ai_service.dart';
@@ -64,6 +65,9 @@ class InitialBinding extends Bindings {
// 会议助手音频上传
Get.lazyPut(() => UserportraitManager(), fenix: true);
+ // 新手引导
+ Get.lazyPut(() => NoviceGuideService(), fenix: true);
+
// 杰理蓝牙服务
// // 语言管理器(需要最先初始化)
diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart
new file mode 100644
index 000000000..060bb5c29
--- /dev/null
+++ b/lib/data/services/novice_guide_service.dart
@@ -0,0 +1,34 @@
+import 'package:flutter_intro/flutter_intro.dart';
+import 'package:get/get.dart';
+import 'package:get_storage/get_storage.dart';
+
+class NoviceGuideService extends GetxService {
+ final GetStorage _storage = GetStorage();
+
+ bool isHomeGuide = false;
+ bool isAIGuide = false;
+
+ @override
+ void onInit() {
+ super.onInit();
+ // isHomeGuide = _storage.read('home_guide') ?? false;
+ // isAIGuide = _storage.read('ai_guide') ?? false;
+ }
+
+ void startHomeGuide() {
+ if (!isHomeGuide) {
+ isHomeGuide = true;
+ _storage.write('home_guide', true);
+ Intro.of(Get.context!).start(group: 'home');
+ }
+ }
+
+ void startAIGuide() async {
+ if (!isAIGuide) {
+ await Future.delayed(const Duration(milliseconds: 200));
+ isAIGuide = true;
+ _storage.write('ai_guide', true);
+ Intro.of(Get.context!).start(group: 'ai');
+ }
+ }
+}
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index 149c05b22..5330abd4c 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:amap_service/amap_service.dart';
+import '../../../data/services/novice_guide_service.dart';
import '/core/utils/upload_oss.dart';
import '/data/models/user_Info.dart';
import '/data/services/music_manager.dart';
@@ -39,6 +40,8 @@ class AgentController extends GetxController {
final TextEditingController textController = TextEditingController();
final ScrollController scrollController = ScrollController();
+ final noviceGuideService = Get.find();
+
//音乐播放管理
final _musiceManager = Get.find();
diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart
index 0ef9a63ac..d989cd037 100644
--- a/lib/modules/agent/views/agent_view.dart
+++ b/lib/modules/agent/views/agent_view.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
+import 'package:flutter_intro/flutter_intro.dart';
import 'package:get/get.dart';
import '../controllers/agent_controller.dart';
import '../controllers/message_action_controller.dart';
@@ -388,96 +389,110 @@ class _AgentViewState extends State with WidgetsBindingObserver {
if (controller.isTextInputMode.value) {
// 文本输入模式 - 限制最大高度并只向上扩展
- return Container(
- constraints: BoxConstraints(
- minHeight: 44.h,
- maxHeight: 120.h,
- ),
- padding: EdgeInsets.symmetric(
- horizontal: 16.w),
- decoration: BoxDecoration(
- color: isDarkMode
- ? Colors.grey[800]
- : Colors.grey[200],
- borderRadius:
- BorderRadius.circular(22.w),
- border: Border.all(
+ return IntroStepBuilder(
+ group: 'ai',
+ order: 1,
+ text: '对小言说“今天天气怎么样”',
+ onWidgetLoad: () {
+ controller.noviceGuideService
+ .startAIGuide();
+ },
+ builder: (BuildContext context,
+ GlobalKey key) =>
+ Container(
+ key: key,
+ constraints: BoxConstraints(
+ minHeight: 44.h,
+ maxHeight: 120.h,
+ ),
+ padding: EdgeInsets.symmetric(
+ horizontal: 16.w),
+ decoration: BoxDecoration(
color: isDarkMode
- ? Colors.grey[800]!
- : Colors.grey.withOpacity(0.1),
- width: 1.w,
+ ? Colors.grey[800]
+ : Colors.grey[200],
+ borderRadius:
+ BorderRadius.circular(22.w),
+ border: Border.all(
+ color: isDarkMode
+ ? Colors.grey[800]!
+ : Colors.grey.withOpacity(0.1),
+ width: 1.w,
+ ),
),
- ),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment
- .end, // 对齐到底部,实现向上扩展
- children: [
- Expanded(
- child: Obx(() {
- // 图片模式下使用更高的输入框
- final isImageActive = controller
- .isImageInputActive.value;
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment
+ .end, // 对齐到底部,实现向上扩展
+ children: [
+ Expanded(
+ child: Obx(() {
+ // 图片模式下使用更高的输入框
+ final isImageActive = controller
+ .isImageInputActive.value;
- return TextField(
- controller:
- controller.textController,
- maxLines: isImageActive
- ? 3
- : 5, // 设置合理的最大行数
- minLines: isImageActive ? 2 : 1,
- keyboardType: TextInputType
- .multiline, // 启用多行键盘
- textInputAction: TextInputAction
- .newline, // 支持回车换行
- onTap: () {
- // 当点击输入框时,如果'+'选项已展开,则收缩
- if (controller
- .isPlusOptionsExpanded
- .value) {
- controller
- .collapsePlusOptions();
- }
- },
- style: TextStyle(
- fontSize: 15.sp,
- color: isDarkMode
- ? Colors.white
- : Colors.grey[800],
- ),
- decoration: InputDecoration(
- hintText: isImageActive
- ? 'addQuestionAboutImage'
- .tr // 添加对图片的问题或描述...
- : 'inputMessage'
- .tr, // 输入消息...
- hintStyle: TextStyle(
- color: Colors.grey[500],
+ return TextField(
+ controller:
+ controller.textController,
+ maxLines: isImageActive
+ ? 3
+ : 5, // 设置合理的最大行数
+ minLines:
+ isImageActive ? 2 : 1,
+ keyboardType: TextInputType
+ .multiline, // 启用多行键盘
+ textInputAction:
+ TextInputAction
+ .newline, // 支持回车换行
+ onTap: () {
+ // 当点击输入框时,如果'+'选项已展开,则收缩
+ if (controller
+ .isPlusOptionsExpanded
+ .value) {
+ controller
+ .collapsePlusOptions();
+ }
+ },
+ style: TextStyle(
fontSize: 15.sp,
+ color: isDarkMode
+ ? Colors.white
+ : Colors.grey[800],
),
- filled: false,
- border: InputBorder.none,
- focusedBorder:
- OutlineInputBorder(
- borderSide: BorderSide(
- color: isDarkMode
- ? Colors.grey[800]!
- : Colors.grey[200]!,
- width: 1.w,
+ decoration: InputDecoration(
+ hintText: isImageActive
+ ? 'addQuestionAboutImage'
+ .tr // 添加对图片的问题或描述...
+ : 'inputMessage'
+ .tr, // 输入消息...
+ hintStyle: TextStyle(
+ color: Colors.grey[500],
+ fontSize: 15.sp,
+ ),
+ filled: false,
+ border: InputBorder.none,
+ focusedBorder:
+ OutlineInputBorder(
+ borderSide: BorderSide(
+ color: isDarkMode
+ ? Colors.grey[800]!
+ : Colors.grey[200]!,
+ width: 1.w,
+ ),
),
+ contentPadding:
+ EdgeInsets.symmetric(
+ vertical:
+ isImageActive
+ ? 12.h
+ : 8.h),
),
- contentPadding:
- EdgeInsets.symmetric(
- vertical:
- isImageActive
- ? 12.h
- : 8.h),
- ),
- onSubmitted: (_) =>
- controller.sendMessage(),
- );
- }),
- ),
- ],
+ onSubmitted: (_) =>
+ controller.sendMessage(),
+ );
+ }),
+ ),
+ ],
+ ),
),
);
} else {
@@ -919,21 +934,19 @@ class _AgentViewState extends State with WidgetsBindingObserver {
Widget _buildPhoneCallOverlay(BuildContext context, bool isDarkMode) {
return Container(
color: Colors.transparent, // 设置整个覆盖层为透明
- child: SafeArea(
- child: Column(
- children: [
- // 中间空白区域 - 完全透明
- const Expanded(
- child: SizedBox(), // 空的占位符,完全透明
- ),
+ child: Column(
+ children: [
+ // 中间空白区域 - 完全透明
+ const Expanded(
+ child: SizedBox(), // 空的占位符,完全透明
+ ),
- // 底部控制按钮 - 包含音频波纹
- Container(
- color: isDarkMode ? Colors.black : Colors.white,
- child: _buildPhoneCallControls(isDarkMode),
- ),
- ],
- ),
+ // 底部控制按钮 - 包含音频波纹
+ Container(
+ color: isDarkMode ? Colors.black : Colors.white,
+ child: _buildPhoneCallControls(isDarkMode),
+ ),
+ ],
),
);
}
diff --git a/lib/modules/devices/controllers/devices_controller.dart b/lib/modules/devices/controllers/devices_controller.dart
index 1beadea7a..66ff69211 100644
--- a/lib/modules/devices/controllers/devices_controller.dart
+++ b/lib/modules/devices/controllers/devices_controller.dart
@@ -57,6 +57,7 @@ class DevicesController extends GetxController {
User.instance.removeUserDevice(device.id);
_bleManager.clearPairedDevices();
_bleManager.disconnect();
+ await Future.delayed(const Duration(seconds: 1));
Get.toNamed(Routes.pairing);
return;
} else {
diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart
index 3f952483e..902ceb58d 100644
--- a/lib/modules/home/controllers/home_controller.dart
+++ b/lib/modules/home/controllers/home_controller.dart
@@ -1,5 +1,6 @@
import 'package:get/get.dart';
import 'dart:async';
+import '../../../data/services/novice_guide_service.dart';
import '../../../routes/app_routes.dart';
import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart';
@@ -21,7 +22,7 @@ class HomeController extends GetxController {
final _isFullyConnected = false.obs;
String? _lastConnectedDeviceName;
-
+
bool get isFullyConnected => _isFullyConnected.value;
RxBool get isFullyConnectedRx => _isFullyConnected;
BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice;
@@ -50,6 +51,9 @@ class HomeController extends GetxController {
final _bluetoothManager = Get.find();
final _bleManager = Get.find();
+
+ final noviceGuideService = Get.find();
+
@override
void onInit() {
super.onInit();
@@ -77,13 +81,14 @@ class HomeController extends GetxController {
update();
});
- _classicBluetoothWorker = ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
+ _classicBluetoothWorker =
+ ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) {
_lastConnectedDeviceName = device.name;
}
_checkConnectionStatus();
});
-
+
_bleConnectionWorker = ever(_bleManager.connectionStateRx, (int state) {
_checkConnectionStatus();
});
@@ -104,30 +109,30 @@ class HomeController extends GetxController {
void exploreAIFeatures() {
Get.toNamed(Routes.agent);
}
-
+
Future _checkConnectionStatus() async {
try {
final currentBleState = await _bleManager.getConnectionState();
-
+
bool hasClassicBluetooth = currentDevice != null;
bool hasBleConnection = currentBleState == 2;
bool wasConnected = _isFullyConnected.value;
bool isNowConnected = hasClassicBluetooth && hasBleConnection;
-
+
if (isNowConnected && currentDevice == null) {
isNowConnected = false;
}
-
+
if (_isFullyConnected.value != isNowConnected) {
_isFullyConnected.value = isNowConnected;
_isFullyConnected.refresh();
update();
-
+
if (!wasConnected && isNowConnected) {
await _bleManager.fetchDeviceInfo();
}
}
-
+
update();
} catch (e) {
Logger.e(_tag, '${'checkConnectionStatusFailed'.tr}: $e');
@@ -148,26 +153,28 @@ class HomeController extends GetxController {
}
bool get shouldShowDeviceInfo => currentDevice != null;
-
+
String getDeviceName() {
if (_isFullyConnected.value && currentDevice == null) {
_checkConnectionStatus();
}
-
+
final deviceName = currentDevice?.name;
if (deviceName != null && deviceName.isNotEmpty) {
return deviceName;
}
-
+
final bleDeviceName = deviceInfo.value?.name;
if (bleDeviceName != null && bleDeviceName.isNotEmpty) {
return bleDeviceName;
}
-
- if (!_isFullyConnected.value && _lastConnectedDeviceName != null && _lastConnectedDeviceName!.isNotEmpty) {
+
+ if (!_isFullyConnected.value &&
+ _lastConnectedDeviceName != null &&
+ _lastConnectedDeviceName!.isNotEmpty) {
return _lastConnectedDeviceName!;
}
-
+
return 'unknownEquipment'.tr;
}
@@ -253,5 +260,4 @@ class HomeController extends GetxController {
? deviceInfo.value!.firmwareVersion
: 'unknown'.tr;
}
-
}
diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart
index 18b24b210..f842b6a23 100644
--- a/lib/modules/home/views/home_view.dart
+++ b/lib/modules/home/views/home_view.dart
@@ -84,11 +84,30 @@ class HomeView extends GetView {
_buildAppBar(isDarkMode),
// 顶部图片
- _buildEarbudsImage(),
+ IntroStepBuilder(
+ group: 'home',
+ order: 1,
+ text: '欢迎使用Voitrans!首先,让我们唤醒你的专属 AI—— 小言',
+ onWidgetLoad: () {
+ controller.noviceGuideService.startHomeGuide();
+ },
+ builder: (BuildContext context, GlobalKey key) => SizedBox(
+ key: key,
+ child: _buildEarbudsImage(),
+ ),
+ ),
SizedBox(height: 20.h),
// 设备状态卡片
- _buildDeviceStatusCard(isDarkMode),
+ IntroStepBuilder(
+ group: 'home',
+ order: 2,
+ text: '对着麦克风说“小言小言”',
+ builder: (BuildContext context, GlobalKey key) => SizedBox(
+ key: key,
+ child: _buildDeviceStatusCard(isDarkMode),
+ ),
+ ),
SizedBox(height: 20.h),
// 热门AI体验
@@ -624,8 +643,9 @@ class HomeView extends GetView {
Expanded(
flex: 1,
child: IntroStepBuilder(
+ group: 'home',
order: 3,
- text: 'AI全能助手',
+ text: '太棒了!你已成功唤醒小言,接下来试试让它帮你做件事吧',
onHighlightWidgetTap: () {
Intro.of(Get.context!).dispose();
controller.exploreAIFeatures();
@@ -660,62 +680,47 @@ class HomeView extends GetView {
child: Column(
children: [
// 右上卡片 - 同声翻译
- IntroStepBuilder(
- order: 1,
- text: '同声传译',
- // onWidgetLoad: () {
- // Intro.of(Get.context!).start();
- // },
- builder: (BuildContext context, GlobalKey key) =>
- _buildFeatureCard(
- key: key,
- title: 'simultaneousInterpretation'.tr, // 同声传译
- description:
- 'simultaneousInterpretationInfo'.tr, // 80+语言实时翻译,出国无忧
- icon: Icons.translate,
- gradient: LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [
- const Color(0xFF4CAF50).withOpacity(0.3),
- const Color(0xFF009688).withOpacity(0.3),
- ],
- ),
- onTap: controller.openTranslation,
- height: 83.h, // 增加高度
- isDarkMode: isDarkMode,
- iconColor: const Color(0xFF4CAF50),
+ _buildFeatureCard(
+ title: 'simultaneousInterpretation'.tr, // 同声传译
+ description:
+ 'simultaneousInterpretationInfo'.tr, // 80+语言实时翻译,出国无忧
+ icon: Icons.translate,
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ const Color(0xFF4CAF50).withOpacity(0.3),
+ const Color(0xFF009688).withOpacity(0.3),
+ ],
),
+ onTap: controller.openTranslation,
+ height: 83.h, // 增加高度
+ isDarkMode: isDarkMode,
+ iconColor: const Color(0xFF4CAF50),
),
SizedBox(height: 5.h), // 减少间距
// 右下卡片 -
// 右下卡片 - 会议助手
- IntroStepBuilder(
- order: 2,
- text: '商务会议助手',
- builder: (BuildContext context, GlobalKey key) =>
- _buildFeatureCard(
- key: key,
- title: 'conferenceAssistant'.tr, // 商务会议助手
- description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
- icon: Icons.people,
- gradient: LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [
- Color(0xFFab47bc).withOpacity(0.3), // 薰衣草紫
- Color(0xFFec407a).withOpacity(0.3), // 品红
- ],
- ),
- onTap: () {
- Get.toNamed(Routes.meeting);
- },
- height: 85.h, // 增加高度
- isDarkMode: isDarkMode,
- iconColor: const Color(0xFFd81b60), // 品红
+ _buildFeatureCard(
+ title: 'conferenceAssistant'.tr, // 商务会议助手
+ description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
+ icon: Icons.people,
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ Color(0xFFab47bc).withOpacity(0.3), // 薰衣草紫
+ Color(0xFFec407a).withOpacity(0.3), // 品红
+ ],
),
+ onTap: () {
+ Get.toNamed(Routes.meeting);
+ },
+ height: 85.h, // 增加高度
+ isDarkMode: isDarkMode,
+ iconColor: const Color(0xFFd81b60), // 品红
),
],
),
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index d267211f3..dedb8fbbb 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -16,6 +16,7 @@ import 'dart:io';
import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/ble_manager.dart';
import '../../../core/utils/permission_util.dart';
+import 'package:url_launcher/url_launcher.dart';
/// 配对状态枚举 - 简化版本
enum PairingState {
@@ -202,7 +203,7 @@ class PairingController extends GetxController {
case PairingState.waitingClassic:
if (device != null) {
// 有设备连接,进入验证阶段
- Logger.i(_tag, '检测到经典蓝牙设备连接,开始验证');
+ Logger.d(_tag, '检测到经典蓝牙设备连接,开始验证');
_transition(PairingState.verifying);
_verifyDevice(device);
}
@@ -623,7 +624,7 @@ class PairingController extends GetxController {
);
product = match.isNotEmpty ? match.first : null;
}
-
+ final currentBleState = await _bleManager.getConnectionState();
final names = AppConfig.allproductNames();
// 检查是否已有绑定的设备
print("device.address${device.address}");
@@ -645,6 +646,17 @@ class PairingController extends GetxController {
// 已经绑定
return 9;
}
+ } else {
+ //未绑定设备
+
+ Logger.i(_tag, '验证时BLE状态: $currentBleState');
+ if (currentBleState == 2) {
+ //已连接 清理
+ // BleConnectionState.connected
+ Logger.i(_tag, 'BLE已连接,断开并清理缓存');
+ _bleManager.clearPairedDevices();
+ _bleManager.disconnect();
+ }
}
if (Platform.isAndroid) {
@@ -672,11 +684,15 @@ class PairingController extends GetxController {
print(
'获取到 deviceName: $deviceName address: ${device.address} names:$names');
if (names.contains(deviceName)) {
+ if (currentBleState == 2) {
+ //已连接直接返回
+ return 9;
+ }
await _bleManager.startScan(deviceName);
await Future.delayed(const Duration(seconds: 1));
// 尝试最多读取 3 次设备信息,每次间隔 1 秒
List