From b50cdb1f78b70694e555be0142369fd98690823b Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 31 Jul 2025 15:47:05 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor(version):=20=E5=B0=86=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91=E4=BB=8Ehome?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=88=B0splash=E6=8E=A7=E5=88=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将版本更新检查功能从HomeController迁移到SplashController,使其在应用启动时执行 移除HomeController中不再需要的相关代码和依赖 --- .../home/controllers/home_controller.dart | 116 --------------- .../splash/controllers/splash_controller.dart | 137 +++++++++++++++--- 2 files changed, 116 insertions(+), 137 deletions(-) diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 31db67e15..3f952483e 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -5,9 +5,6 @@ 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 { @@ -53,14 +50,6 @@ class HomeController extends GetxController { final _bluetoothManager = Get.find(); final _bleManager = Get.find(); - final _storage = GetStorage(); - - // 会话级别的版本检查标记(静态变量,整个应用会话期间保持) - static bool _hasCheckedVersionInSession = false; - - // 版本检查相关常量 - static const String _lastVersionCheckKey = 'last_version_check_timestamp'; - static const int _versionCheckIntervalHours = 24; // 24小时间隔 @override void onInit() { super.onInit(); @@ -78,12 +67,6 @@ class HomeController extends GetxController { void _initWithCachedInfo() { _setupStateListeners(); _checkConnectionStatus(); - - // 延迟检查版本更新,避免影响页面加载 - // 混合检查机制:会话检查 + 24小时间隔检查 - Future.delayed(const Duration(seconds: 2), () { - _checkVersionUpdateIfNeeded(); - }); } void _setupStateListeners() { @@ -271,103 +254,4 @@ class HomeController extends GetxController { : '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/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index ba834cdc1..4badc4d43 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,7 +59,7 @@ class SplashController extends GetxController { } }); - //弹出用户追中问题 + //弹出用户追踪问题 WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) async { final status = await AppTrackingTransparency.requestTrackingAuthorization(); @@ -60,12 +69,17 @@ class SplashController extends GetxController { void _startNormalFlow() { _startLoadingAnimation(); _loadAppResources(); + + // 延迟执行版本检查,避免影响启动速度 + Future.delayed(const Duration(seconds: 1), () { + _checkVersionUpdateIfNeeded(); + }); } void _onPrivacyAccepted() { _storage.write('hasAcceptedPrivacy', true); Get.back(); // 关闭弹窗 - _startNormalFlow(); // 开始正常流程 + _startNormalFlow(); // 开始正常流程(已包含版本检查) } void _onPrivacyRejected() { @@ -205,22 +219,103 @@ class SplashController extends GetxController { } } - /// 检查版本更新(后台执行,不阻塞启动流程) - // 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; + 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('SplashCtrl', '版本检查会话标记已重置'); + } + + /// 完全重置版本检查状态(包括时间戳) + /// 用于测试或特殊情况下强制重新检查 + void forceResetVersionCheck() { + try { + _hasCheckedVersionInSession = false; + _storage.remove(_lastVersionCheckKey); + Logger.i(_tag, '版本检查状态已完全重置'); + } catch (e) { + Logger.e(_tag, '重置版本检查状态失败: $e'); + } + } } From a3bf2585b0255c74f587bcac96bf4eb0ce921cc0 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 11:47:55 +0800 Subject: [PATCH 2/5] =?UTF-8?q?refactor(splash):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=B5=81=E7=A8=8B=E5=92=8C=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E8=B0=83=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将用户追踪权限请求改为异步执行不阻塞主流程 重构版本检查逻辑,在页面跳转完成后执行 使用Logger替代print进行统一日志管理 --- .../splash/controllers/splash_controller.dart | 106 ++++++++++-------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 4badc4d43..4bf70d207 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -59,20 +59,37 @@ 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'); + } }); } void _startNormalFlow() { _startLoadingAnimation(); _loadAppResources(); + } - // 延迟执行版本检查,避免影响启动速度 - Future.delayed(const Duration(seconds: 1), () { - _checkVersionUpdateIfNeeded(); + /// 智能调度版本检查,在页面跳转后执行,完全不影响启动流程 + void _scheduleVersionCheck() { + // 在独立的微任务中执行,避免阻塞主线程 + scheduleMicrotask(() async { + try { + Logger.d(_tag, '开始执行版本检查(后台任务)'); + _checkVersionUpdateIfNeeded(); + } catch (e) { + Logger.e(_tag, '版本检查调度失败: $e'); + } }); } @@ -98,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秒 @@ -136,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(); } } @@ -159,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(); } @@ -173,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); @@ -192,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); @@ -214,7 +220,7 @@ class SplashController extends GetxController { AppConfig.initialize(resp); return true; } catch (e) { - print('getAppConfig 取失败: $e'); + Logger.e(_tag, 'getAppConfig 取失败: $e'); return false; } } @@ -236,7 +242,8 @@ class SplashController 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) { @@ -265,18 +272,25 @@ class SplashController extends GetxController { 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}'); - } + // 异步执行,不等待结果 + 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(); + // 更新检查记录 + _updateVersionCheckRecord(); + }).catchError((e) { + Logger.e(_tag, '版本检查失败: $e'); + // 即使检查失败,也更新记录,避免频繁重试 + _updateVersionCheckRecord(); + }); } catch (e) { Logger.e(_tag, '版本检查失败: $e'); // 即使检查失败,也更新记录,避免频繁重试 From d1b99d99dcc7788863dfe5b923063dd8cab33a0d Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 1 Aug 2025 14:41:12 +0800 Subject: [PATCH 3/5] =?UTF-8?q?refactor(widget):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=A3=B0=E9=9F=B3=E6=9F=B1=E5=8A=A8=E7=94=BB=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 AnimatedBuilder 替代直接监听动画状态 - 添加组件挂载检查防止内存泄漏 - 改进动画停止时的过渡效果 - 更新相关注释说明 --- .../agent/widgets/sound_bar_widget.dart | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) 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; 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 4/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=B0=E6=89=8B?= =?UTF-8?q?=E6=8C=87=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), // 品红 ), ], ), From 762a41528944304205b2bb801de69522ed4da938 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Fri, 1 Aug 2025 17:04:26 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat(ios):=20=E6=B7=BB=E5=8A=A0=20telephony?= =?UTF-8?q?=20=E8=AE=BE=E5=A4=87=E8=83=BD=E5=8A=9B=E8=A6=81=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 Info.plist 中添加 UIRequiredDeviceCapabilities 字段,声明应用需要 telephony 功能支持 --- ios/Runner/Info.plist | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 05d554d39..99b4642a3 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -119,6 +119,10 @@ audio location + UIRequiredDeviceCapabilities + + telephony + UIDeviceFamily 1