From 7bdda09d005c344b790ce7cf460346bdbf0fb689 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 1 Aug 2025 16:40:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=B0=E6=89=8B=E6=8C=87?= =?UTF-8?q?=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/bindings/initial_binding.dart | 4 + lib/data/services/novice_guide_service.dart | 34 ++++ .../agent/controllers/agent_controller.dart | 3 + lib/modules/agent/views/agent_view.dart | 181 ++++++++++-------- .../home/controllers/home_controller.dart | 40 ++-- lib/modules/home/views/home_view.dart | 107 ++++++----- 6 files changed, 219 insertions(+), 150 deletions(-) create mode 100644 lib/data/services/novice_guide_service.dart 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..56ab6464b --- /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: 100)); + 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..ff5da53fd 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 { diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 31db67e15..1287e6c45 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'; @@ -24,7 +25,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; @@ -55,12 +56,15 @@ class HomeController extends GetxController { final _bleManager = Get.find(); final _storage = GetStorage(); + final noviceGuideService = Get.find(); + // 会话级别的版本检查标记(静态变量,整个应用会话期间保持) static bool _hasCheckedVersionInSession = false; // 版本检查相关常量 static const String _lastVersionCheckKey = 'last_version_check_timestamp'; static const int _versionCheckIntervalHours = 24; // 24小时间隔 + @override void onInit() { super.onInit(); @@ -94,13 +98,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(); }); @@ -121,30 +126,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'); @@ -165,26 +170,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; } @@ -288,7 +295,8 @@ class HomeController extends GetxController { } // 条件2:距离上次检查超过24小时 else if (lastCheckTimestamp != null) { - final lastCheckTime = DateTime.fromMillisecondsSinceEpoch(lastCheckTimestamp); + final lastCheckTime = + DateTime.fromMillisecondsSinceEpoch(lastCheckTimestamp); final timeDifference = now.difference(lastCheckTime); if (timeDifference.inHours >= _versionCheckIntervalHours) { 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), // 品红 ), ], ),