diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 75a82d84d..f3ad0d225 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -121,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..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/agent/widgets/sound_bar_widget.dart b/lib/modules/agent/widgets/sound_bar_widget.dart
index 11fb9ed86..9028f713b 100644
--- a/lib/modules/agent/widgets/sound_bar_widget.dart
+++ b/lib/modules/agent/widgets/sound_bar_widget.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/agent_controller.dart';
-/// 声音柱动画组件
+/// 声音柱动画组件 (已优化)
/// 用于显示语音输入时的音频波形效果
class SoundBarWidget extends StatefulWidget {
final double delay;
@@ -37,9 +37,8 @@ class _SoundBarWidgetState extends State
parent: _controller,
curve: Curves.easeInOut,
),
- )..addListener(() {
- setState(() {});
- });
+ );
+ // 移除了 .addListener(() { setState(() {}); })
_startOrStopAnimation();
}
@@ -54,6 +53,9 @@ class _SoundBarWidgetState extends State
/// 开始或停止动画
void _startOrStopAnimation() {
+ // 确保组件仍然挂载在树上
+ if (!mounted) return;
+
if (widget.isActive) {
Future.delayed(Duration(milliseconds: (widget.delay * 1000).toInt()), () {
if (mounted && widget.isActive) {
@@ -61,9 +63,10 @@ class _SoundBarWidgetState extends State
}
});
} else {
- _controller.reset();
- // 设置为小高度的静态状态
- _controller.animateTo(0.1, duration: const Duration(milliseconds: 300));
+ // 停止并平滑地过渡到初始状态
+ _controller.stop();
+ _controller.animateTo(0.0,
+ duration: const Duration(milliseconds: 200), curve: Curves.easeOut);
}
}
@@ -77,21 +80,28 @@ class _SoundBarWidgetState extends State
Widget build(BuildContext context) {
final primaryColor = Theme.of(context).primaryColor;
- return Container(
- width: 2.5,
- height: widget.isActive ? _animation.value : 3,
- margin: const EdgeInsets.symmetric(horizontal: 1),
- decoration: BoxDecoration(
- color: widget.isActive
- ? primaryColor.withOpacity(0.8)
- : Colors.grey.withOpacity(0.4),
- borderRadius: BorderRadius.circular(1.5),
- ),
+ // 使用 AnimatedBuilder 来优化性能
+ return AnimatedBuilder(
+ animation: _animation,
+ builder: (context, child) {
+ return Container(
+ width: 2.5,
+ // 当动画不活跃时,高度固定为3;活跃时使用动画值
+ height: widget.isActive ? _animation.value : 3,
+ margin: const EdgeInsets.symmetric(horizontal: 1),
+ decoration: BoxDecoration(
+ color: widget.isActive
+ ? primaryColor.withOpacity(0.8)
+ : Colors.grey.withOpacity(0.4),
+ borderRadius: BorderRadius.circular(1.5),
+ ),
+ );
+ },
);
}
}
-/// 声音柱组合组件
+/// 声音柱组合组件 (无需改动)
/// 包含多个声音柱的组合显示
class SoundBarsWidget extends StatelessWidget {
final bool isActive;
@@ -120,7 +130,7 @@ class SoundBarsWidget extends StatelessWidget {
}
}
-/// 带控制器集成的声音柱组件
+/// 带控制器集成的声音柱组件 (无需改动)
/// 自动从AgentController获取状态
class AgentSoundBarsWidget extends StatelessWidget {
final int barCount;
diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart
index 31db67e15..902ceb58d 100644
--- a/lib/modules/home/controllers/home_controller.dart
+++ b/lib/modules/home/controllers/home_controller.dart
@@ -1,13 +1,11 @@
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';
import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/ble_manager.dart';
-import '../../../data/services/version_update_service.dart';
-import '../../common/widgets/version_update_dialog.dart';
-import 'package:get_storage/get_storage.dart';
/// 连接状态枚举
enum ConnectionStatus {
@@ -24,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;
@@ -53,14 +51,9 @@ class HomeController extends GetxController {
final _bluetoothManager = Get.find();
final _bleManager = Get.find();
- final _storage = GetStorage();
- // 会话级别的版本检查标记(静态变量,整个应用会话期间保持)
- static bool _hasCheckedVersionInSession = false;
+ final noviceGuideService = Get.find();
- // 版本检查相关常量
- static const String _lastVersionCheckKey = 'last_version_check_timestamp';
- static const int _versionCheckIntervalHours = 24; // 24小时间隔
@override
void onInit() {
super.onInit();
@@ -78,12 +71,6 @@ class HomeController extends GetxController {
void _initWithCachedInfo() {
_setupStateListeners();
_checkConnectionStatus();
-
- // 延迟检查版本更新,避免影响页面加载
- // 混合检查机制:会话检查 + 24小时间隔检查
- Future.delayed(const Duration(seconds: 2), () {
- _checkVersionUpdateIfNeeded();
- });
}
void _setupStateListeners() {
@@ -94,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();
});
@@ -121,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');
@@ -165,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;
}
@@ -270,104 +260,4 @@ class HomeController extends GetxController {
? deviceInfo.value!.firmwareVersion
: 'unknown'.tr;
}
-
- /// 混合检查机制:判断是否需要进行版本检查
- /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时
- void _checkVersionUpdateIfNeeded() {
- try {
- final now = DateTime.now();
- final lastCheckTimestamp = _storage.read(_lastVersionCheckKey);
-
- bool shouldCheck = false;
- String reason = '';
-
- // 条件1:会话内未检查过
- if (!_hasCheckedVersionInSession) {
- shouldCheck = true;
- reason = '会话内首次检查';
- }
- // 条件2:距离上次检查超过24小时
- else if (lastCheckTimestamp != null) {
- final lastCheckTime = DateTime.fromMillisecondsSinceEpoch(lastCheckTimestamp);
- final timeDifference = now.difference(lastCheckTime);
-
- if (timeDifference.inHours >= _versionCheckIntervalHours) {
- shouldCheck = true;
- reason = '距离上次检查已超过${_versionCheckIntervalHours}小时';
- }
- }
- // 条件3:从未检查过(首次使用应用)
- else {
- shouldCheck = true;
- reason = '首次使用应用';
- }
-
- if (shouldCheck) {
- Logger.i(_tag, '开始版本检查 - 原因: $reason');
- _performVersionCheck();
- } else {
- Logger.i(_tag, '跳过版本检查 - 会话内已检查且未超过时间间隔');
- }
- } catch (e) {
- Logger.e(_tag, '版本检查判断失败: $e');
- }
- }
-
- /// 执行版本检查(在后台进行,不阻塞启动流程)
- Future _performVersionCheck() async {
- try {
- final versionService = VersionUpdateService.to;
- final updateInfo = await versionService.checkForUpdate();
-
- if (updateInfo.hasUpdate) {
- Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}');
- // 显示更新对话框
- await VersionUpdateDialog.show(updateInfo);
- } else {
- Logger.i(_tag, '当前已是最新版本: ${updateInfo.currentVersion}');
- }
-
- // 更新检查记录
- _updateVersionCheckRecord();
- } catch (e) {
- Logger.e(_tag, '版本检查失败: $e');
- // 即使检查失败,也更新记录,避免频繁重试
- _updateVersionCheckRecord();
- }
- }
-
- /// 更新版本检查记录
- void _updateVersionCheckRecord() {
- try {
- // 更新时间戳
- final now = DateTime.now();
- _storage.write(_lastVersionCheckKey, now.millisecondsSinceEpoch);
-
- // 标记本次会话已检查过版本
- _hasCheckedVersionInSession = true;
-
- Logger.i(_tag, '版本检查记录已更新: ${now.toIso8601String()}');
- } catch (e) {
- Logger.e(_tag, '更新版本检查记录失败: $e');
- }
- }
-
- /// 重置版本检查标记(可用于强制重新检查)
- /// 注意:这会清除会话标记,但不会清除时间戳记录
- static void resetVersionCheckFlag() {
- _hasCheckedVersionInSession = false;
- Logger.i('HomeCtrl', '版本检查会话标记已重置');
- }
-
- /// 完全重置版本检查状态(包括时间戳)
- /// 用于测试或特殊情况下强制重新检查
- void forceResetVersionCheck() {
- try {
- _hasCheckedVersionInSession = false;
- _storage.remove(_lastVersionCheckKey);
- Logger.i(_tag, '版本检查状态已完全重置');
- } catch (e) {
- Logger.e(_tag, '重置版本检查状态失败: $e');
- }
- }
}
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/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart
index ba834cdc1..4bf70d207 100644
--- a/lib/modules/splash/controllers/splash_controller.dart
+++ b/lib/modules/splash/controllers/splash_controller.dart
@@ -14,10 +14,12 @@ import '../../../data/services/network/api.dart';
import '../../../data/services/version_update_service.dart';
import '../../../modules/common/widgets/version_update_dialog.dart';
import '../../../modules/common/widgets/privacy_policy_dialog.dart';
-import '../../common/webview_page.dart';
+import '../../../core/utils/logger.dart';
class SplashController extends GetxController {
final GetStorage _storage = GetStorage();
+ static const String _tag = 'SplashCtrl';
+
// 进度条动画控制
final _loadingProgress = 0.0.obs;
double get loadingProgress => _loadingProgress.value;
@@ -34,6 +36,13 @@ class SplashController extends GetxController {
// 主题服务
final ThemeService _themeService = ThemeService();
+ // 会话级别的版本检查标记(静态变量,整个应用会话期间保持)
+ static bool _hasCheckedVersionInSession = false;
+
+ // 版本检查相关常量
+ static const String _lastVersionCheckKey = 'last_version_check_timestamp';
+ static const int _versionCheckIntervalHours = 24; // 24小时间隔
+
@override
void onInit() {
super.onInit();
@@ -50,10 +59,19 @@ class SplashController extends GetxController {
}
});
- //弹出用户追中问题
- WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) async {
- final status =
- await AppTrackingTransparency.requestTrackingAuthorization();
+ // 异步处理用户追踪授权,不阻塞主流程
+ _requestTrackingPermissionAsync();
+ }
+
+ /// 异步请求追踪权限,不阻塞启动流程
+ void _requestTrackingPermissionAsync() {
+ Future.delayed(const Duration(milliseconds: 500), () async {
+ try {
+ await AppTrackingTransparency.requestTrackingAuthorization();
+ Logger.d(_tag, '用户追踪权限请求完成');
+ } catch (e) {
+ Logger.e(_tag, '用户追踪权限请求失败: $e');
+ }
});
}
@@ -62,10 +80,23 @@ class SplashController extends GetxController {
_loadAppResources();
}
+ /// 智能调度版本检查,在页面跳转后执行,完全不影响启动流程
+ void _scheduleVersionCheck() {
+ // 在独立的微任务中执行,避免阻塞主线程
+ scheduleMicrotask(() async {
+ try {
+ Logger.d(_tag, '开始执行版本检查(后台任务)');
+ _checkVersionUpdateIfNeeded();
+ } catch (e) {
+ Logger.e(_tag, '版本检查调度失败: $e');
+ }
+ });
+ }
+
void _onPrivacyAccepted() {
_storage.write('hasAcceptedPrivacy', true);
Get.back(); // 关闭弹窗
- _startNormalFlow(); // 开始正常流程
+ _startNormalFlow(); // 开始正常流程(已包含版本检查)
}
void _onPrivacyRejected() {
@@ -84,18 +115,6 @@ class SplashController extends GetxController {
);
}
- // 初始化地图SDK
- // void _initMapSDK() {
- // try {
- // AmapFlutter().initSdk(
- // iosKey: "a4a1394fe817c2f86a424b897b4a9af4",
- // androidKey: "78e7946dd602778df82c6ae143a88184",
- // );
- // } catch (e) {
- // print('地图SDK初始化失败: $e');
- // }
- // }
-
// 进度条动画(持续2秒)
void _startLoadingAnimation() {
const totalDuration = 1500; // 动画总时长增加到1.5秒
@@ -122,22 +141,23 @@ class SplashController extends GetxController {
// await _loadImages();
// await _initDatabase();
- // 检查版本更新(在后台进行,不阻塞启动流程)
- //_checkVersionUpdate();
-
_isResourceLoaded.value = true;
_checkNavigateCondition();
} catch (e) {
Get.snackbar('错误', '资源加载失败,请检查网络连接');
_isResourceLoaded.value = true; // 允许用户继续使用
- print('资源加载错误: $e');
+ Logger.e(_tag, '资源加载错误: $e');
}
}
// 检查跳转条件
- void _checkNavigateCondition() {
+ void _checkNavigateCondition() async {
if (_loadingProgress.value >= 1.0 && _isResourceLoaded.value) {
- _navigateToLogin();
+ // 关键改动:先执行页面跳转,并用 await 等待它完成
+ await _navigateToLogin();
+
+ // 页面跳转完全结束后,再安心地安排版本检查
+ _scheduleVersionCheck();
}
}
@@ -145,7 +165,7 @@ class SplashController extends GetxController {
Future _navigateToLogin() async {
bool isLoggedIn = false;
final token = _storage.read('logintoken');
- print('getLoginToken:进来了${token}');
+ Logger.d(_tag, 'getLoginToken:进来了${token}');
if (token != null && token.isNotEmpty) {
isLoggedIn = await getLoginToken();
}
@@ -159,14 +179,14 @@ class SplashController extends GetxController {
Future getLoginToken() async {
try {
- print('getLoginToken:进来了');
+ Logger.d(_tag, 'getLoginToken:进来了');
//if (User.getname() != "") {
final response = await Api.tokenlogin();
- print('getLoginToken: $response');
+ Logger.d(_tag, 'getLoginToken: $response');
if (response == null) return false;
if (response['user'] != '') {
- print('getLoginToken:进来了');
+ Logger.d(_tag, 'getLoginToken:进来了');
// 根据API设计调整
final user = User.fromJson(response);
@@ -178,21 +198,21 @@ class SplashController extends GetxController {
//}
return false;
} catch (e) {
- print('Token获取失败: $e');
+ Logger.e(_tag, 'Token获取失败: $e');
return false;
}
}
Future getAppConfig() async {
try {
- print('getAppConfig:进来了');
+ Logger.d(_tag, 'getAppConfig:进来了');
//if (User.getname() != "") {
final response = await Api.getappconfig();
- print('getAppConfig: $response');
+ Logger.d(_tag, 'getAppConfig: $response');
if (response == null) return false;
// 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response);
- print('getAppConfig:进来了');
+ Logger.d(_tag, 'getAppConfig:进来了');
// 存储用户信息(示例)
// _storage.write('ENV', resp.env);
// _storage.write('AGENTS', resp.agents);
@@ -200,27 +220,116 @@ class SplashController extends GetxController {
AppConfig.initialize(resp);
return true;
} catch (e) {
- print('getAppConfig 取失败: $e');
+ Logger.e(_tag, 'getAppConfig 取失败: $e');
return false;
}
}
- /// 检查版本更新(后台执行,不阻塞启动流程)
- // void _checkVersionUpdate() {
- // // 延迟执行版本检查,避免影响启动速度
- // Timer(const Duration(seconds: 1), () async {
- // try {
- // final versionService = VersionUpdateService.to;
- // final updateInfo = await versionService.checkForUpdate();
-
- // if (updateInfo.hasUpdate) {
- // // 显示更新对话框
- // await VersionUpdateDialog.show(updateInfo);
- // }
- // } catch (e) {
- // print('版本检查失败: $e');
- // // 版本检查失败不影响用户使用,静默处理
- // }
- // });
- // }
+ /// 混合检查机制:判断是否需要进行版本检查
+ /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时
+ void _checkVersionUpdateIfNeeded() {
+ try {
+ final now = DateTime.now();
+ final lastCheckTimestamp = _storage.read(_lastVersionCheckKey);
+
+ bool shouldCheck = false;
+ String reason = '';
+
+ // 条件1:会话内未检查过
+ if (!_hasCheckedVersionInSession) {
+ shouldCheck = true;
+ reason = '会话内首次检查';
+ }
+ // 条件2:距离上次检查超过24小时
+ else if (lastCheckTimestamp != null) {
+ final lastCheckTime =
+ DateTime.fromMillisecondsSinceEpoch(lastCheckTimestamp);
+ final timeDifference = now.difference(lastCheckTime);
+
+ if (timeDifference.inHours >= _versionCheckIntervalHours) {
+ shouldCheck = true;
+ reason = '距离上次检查已超过${_versionCheckIntervalHours}小时';
+ }
+ }
+ // 条件3:从未检查过(首次使用应用)
+ else {
+ shouldCheck = true;
+ reason = '首次使用应用';
+ }
+
+ if (shouldCheck) {
+ Logger.i(_tag, '开始版本检查 - 原因: $reason');
+ _performVersionCheck();
+ } else {
+ Logger.i(_tag, '跳过版本检查 - 会话内已检查且未超过时间间隔');
+ }
+ } catch (e) {
+ Logger.e(_tag, '版本检查判断失败: $e');
+ }
+ }
+
+ /// 执行版本检查(在后台进行,不阻塞启动流程)
+ Future _performVersionCheck() async {
+ try {
+ final versionService = VersionUpdateService.to;
+ // 异步执行,不等待结果
+ versionService.checkForUpdate().then((updateInfo) {
+ if (updateInfo.hasUpdate) {
+ Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}');
+ // 在主线程中显示更新对话框
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ VersionUpdateDialog.show(updateInfo);
+ });
+ } else {
+ Logger.i(_tag, '当前已是最新版本: ${updateInfo.currentVersion}');
+ }
+
+ // 更新检查记录
+ _updateVersionCheckRecord();
+ }).catchError((e) {
+ Logger.e(_tag, '版本检查失败: $e');
+ // 即使检查失败,也更新记录,避免频繁重试
+ _updateVersionCheckRecord();
+ });
+ } catch (e) {
+ Logger.e(_tag, '版本检查失败: $e');
+ // 即使检查失败,也更新记录,避免频繁重试
+ _updateVersionCheckRecord();
+ }
+ }
+
+ /// 更新版本检查记录
+ void _updateVersionCheckRecord() {
+ try {
+ // 更新时间戳
+ final now = DateTime.now();
+ _storage.write(_lastVersionCheckKey, now.millisecondsSinceEpoch);
+
+ // 标记本次会话已检查过版本
+ _hasCheckedVersionInSession = true;
+
+ Logger.i(_tag, '版本检查记录已更新: ${now.toIso8601String()}');
+ } catch (e) {
+ Logger.e(_tag, '更新版本检查记录失败: $e');
+ }
+ }
+
+ /// 重置版本检查标记(可用于强制重新检查)
+ /// 注意:这会清除会话标记,但不会清除时间戳记录
+ static void resetVersionCheckFlag() {
+ _hasCheckedVersionInSession = false;
+ Logger.i('SplashCtrl', '版本检查会话标记已重置');
+ }
+
+ /// 完全重置版本检查状态(包括时间戳)
+ /// 用于测试或特殊情况下强制重新检查
+ void forceResetVersionCheck() {
+ try {
+ _hasCheckedVersionInSession = false;
+ _storage.remove(_lastVersionCheckKey);
+ Logger.i(_tag, '版本检查状态已完全重置');
+ } catch (e) {
+ Logger.e(_tag, '重置版本检查状态失败: $e');
+ }
+ }
}