Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
liwei1dao 1 year ago
parent
commit
9fcedbe97f
  1. 4
      ios/Runner/Info.plist
  2. 4
      lib/core/bindings/initial_binding.dart
  3. 34
      lib/data/services/novice_guide_service.dart
  4. 3
      lib/modules/agent/controllers/agent_controller.dart
  5. 181
      lib/modules/agent/views/agent_view.dart
  6. 48
      lib/modules/agent/widgets/sound_bar_widget.dart
  7. 150
      lib/modules/home/controllers/home_controller.dart
  8. 107
      lib/modules/home/views/home_view.dart
  9. 211
      lib/modules/splash/controllers/splash_controller.dart

4
ios/Runner/Info.plist

@ -121,6 +121,10 @@
<string>audio</string> <string>audio</string>
<string>location</string> <string>location</string>
</array> </array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>telephony</string>
</array>
<key>UIDeviceFamily</key> <key>UIDeviceFamily</key>
<array> <array>
<string>1</string> <string>1</string>

4
lib/core/bindings/initial_binding.dart

@ -6,6 +6,7 @@ import '../../../data/services/navigation_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../data/services/meeting/meeting_task_service.dart'; import '../../data/services/meeting/meeting_task_service.dart';
import '../../data/services/meeting/meeting_upload_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/speech_factory.dart';
import '../../data/services/volcano_translation_service.dart'; import '../../data/services/volcano_translation_service.dart';
import '../../data/services/volcano_ai_service.dart'; import '../../data/services/volcano_ai_service.dart';
@ -64,6 +65,9 @@ class InitialBinding extends Bindings {
// 会议助手音频上传 // 会议助手音频上传
Get.lazyPut<UserportraitManager>(() => UserportraitManager(), fenix: true); Get.lazyPut<UserportraitManager>(() => UserportraitManager(), fenix: true);
// 新手引导
Get.lazyPut<NoviceGuideService>(() => NoviceGuideService(), fenix: true);
// 杰理蓝牙服务 // 杰理蓝牙服务
// // 语言管理器(需要最先初始化) // // 语言管理器(需要最先初始化)

34
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');
}
}
}

3
lib/modules/agent/controllers/agent_controller.dart

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:ffi'; import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'package:amap_service/amap_service.dart'; import 'package:amap_service/amap_service.dart';
import '../../../data/services/novice_guide_service.dart';
import '/core/utils/upload_oss.dart'; import '/core/utils/upload_oss.dart';
import '/data/models/user_Info.dart'; import '/data/models/user_Info.dart';
import '/data/services/music_manager.dart'; import '/data/services/music_manager.dart';
@ -39,6 +40,8 @@ class AgentController extends GetxController {
final TextEditingController textController = TextEditingController(); final TextEditingController textController = TextEditingController();
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
final noviceGuideService = Get.find<NoviceGuideService>();
//音乐播放管理 //音乐播放管理
final _musiceManager = Get.find<MusicManager>(); final _musiceManager = Get.find<MusicManager>();

181
lib/modules/agent/views/agent_view.dart

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_intro/flutter_intro.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/agent_controller.dart'; import '../controllers/agent_controller.dart';
import '../controllers/message_action_controller.dart'; import '../controllers/message_action_controller.dart';
@ -388,96 +389,110 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
if (controller.isTextInputMode.value) { if (controller.isTextInputMode.value) {
// 文本输入模式 - 限制最大高度并只向上扩展 // 文本输入模式 - 限制最大高度并只向上扩展
return Container( return IntroStepBuilder(
constraints: BoxConstraints( group: 'ai',
minHeight: 44.h, order: 1,
maxHeight: 120.h, text: '对小言说“今天天气怎么样”',
), onWidgetLoad: () {
padding: EdgeInsets.symmetric( controller.noviceGuideService
horizontal: 16.w), .startAIGuide();
decoration: BoxDecoration( },
color: isDarkMode builder: (BuildContext context,
? Colors.grey[800] GlobalKey key) =>
: Colors.grey[200], Container(
borderRadius: key: key,
BorderRadius.circular(22.w), constraints: BoxConstraints(
border: Border.all( minHeight: 44.h,
maxHeight: 120.h,
),
padding: EdgeInsets.symmetric(
horizontal: 16.w),
decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.grey[800]! ? Colors.grey[800]
: Colors.grey.withOpacity(0.1), : Colors.grey[200],
width: 1.w, borderRadius:
BorderRadius.circular(22.w),
border: Border.all(
color: isDarkMode
? Colors.grey[800]!
: Colors.grey.withOpacity(0.1),
width: 1.w,
),
), ),
), child: Row(
child: Row( crossAxisAlignment: CrossAxisAlignment
crossAxisAlignment: CrossAxisAlignment .end, // 对齐到底部,实现向上扩展
.end, // 对齐到底部,实现向上扩展 children: [
children: [ Expanded(
Expanded( child: Obx(() {
child: Obx(() { // 图片模式下使用更高的输入框
// 图片模式下使用更高的输入框 final isImageActive = controller
final isImageActive = controller .isImageInputActive.value;
.isImageInputActive.value;
return TextField( return TextField(
controller: controller:
controller.textController, controller.textController,
maxLines: isImageActive maxLines: isImageActive
? 3 ? 3
: 5, // 设置合理的最大行数 : 5, // 设置合理的最大行数
minLines: isImageActive ? 2 : 1, minLines:
keyboardType: TextInputType isImageActive ? 2 : 1,
.multiline, // 启用多行键盘 keyboardType: TextInputType
textInputAction: TextInputAction .multiline, // 启用多行键盘
.newline, // 支持回车换行 textInputAction:
onTap: () { TextInputAction
// 当点击输入框时,如果'+'选项已展开,则收缩 .newline, // 支持回车换行
if (controller onTap: () {
.isPlusOptionsExpanded // 当点击输入框时,如果'+'选项已展开,则收缩
.value) { if (controller
controller .isPlusOptionsExpanded
.collapsePlusOptions(); .value) {
} controller
}, .collapsePlusOptions();
style: TextStyle( }
fontSize: 15.sp, },
color: isDarkMode style: TextStyle(
? Colors.white
: Colors.grey[800],
),
decoration: InputDecoration(
hintText: isImageActive
? 'addQuestionAboutImage'
.tr // 添加对图片的问题或描述...
: 'inputMessage'
.tr, // 输入消息...
hintStyle: TextStyle(
color: Colors.grey[500],
fontSize: 15.sp, fontSize: 15.sp,
color: isDarkMode
? Colors.white
: Colors.grey[800],
), ),
filled: false, decoration: InputDecoration(
border: InputBorder.none, hintText: isImageActive
focusedBorder: ? 'addQuestionAboutImage'
OutlineInputBorder( .tr // 添加对图片的问题或描述...
borderSide: BorderSide( : 'inputMessage'
color: isDarkMode .tr, // 输入消息...
? Colors.grey[800]! hintStyle: TextStyle(
: Colors.grey[200]!, color: Colors.grey[500],
width: 1.w, 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: onSubmitted: (_) =>
EdgeInsets.symmetric( controller.sendMessage(),
vertical: );
isImageActive }),
? 12.h ),
: 8.h), ],
), ),
onSubmitted: (_) =>
controller.sendMessage(),
);
}),
),
],
), ),
); );
} else { } else {

48
lib/modules/agent/widgets/sound_bar_widget.dart

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/agent_controller.dart'; import '../controllers/agent_controller.dart';
/// 声音柱动画组件 /// 声音柱动画组件 (已优化)
/// 用于显示语音输入时的音频波形效果 /// 用于显示语音输入时的音频波形效果
class SoundBarWidget extends StatefulWidget { class SoundBarWidget extends StatefulWidget {
final double delay; final double delay;
@ -37,9 +37,8 @@ class _SoundBarWidgetState extends State<SoundBarWidget>
parent: _controller, parent: _controller,
curve: Curves.easeInOut, curve: Curves.easeInOut,
), ),
)..addListener(() { );
setState(() {}); // 移除了 .addListener(() { setState(() {}); })
});
_startOrStopAnimation(); _startOrStopAnimation();
} }
@ -54,6 +53,9 @@ class _SoundBarWidgetState extends State<SoundBarWidget>
/// 开始或停止动画 /// 开始或停止动画
void _startOrStopAnimation() { void _startOrStopAnimation() {
// 确保组件仍然挂载在树上
if (!mounted) return;
if (widget.isActive) { if (widget.isActive) {
Future.delayed(Duration(milliseconds: (widget.delay * 1000).toInt()), () { Future.delayed(Duration(milliseconds: (widget.delay * 1000).toInt()), () {
if (mounted && widget.isActive) { if (mounted && widget.isActive) {
@ -61,9 +63,10 @@ class _SoundBarWidgetState extends State<SoundBarWidget>
} }
}); });
} else { } else {
_controller.reset(); // 停止并平滑地过渡到初始状态
// 设置为小高度的静态状态 _controller.stop();
_controller.animateTo(0.1, duration: const Duration(milliseconds: 300)); _controller.animateTo(0.0,
duration: const Duration(milliseconds: 200), curve: Curves.easeOut);
} }
} }
@ -77,21 +80,28 @@ class _SoundBarWidgetState extends State<SoundBarWidget>
Widget build(BuildContext context) { Widget build(BuildContext context) {
final primaryColor = Theme.of(context).primaryColor; final primaryColor = Theme.of(context).primaryColor;
return Container( // 使用 AnimatedBuilder 来优化性能
width: 2.5, return AnimatedBuilder(
height: widget.isActive ? _animation.value : 3, animation: _animation,
margin: const EdgeInsets.symmetric(horizontal: 1), builder: (context, child) {
decoration: BoxDecoration( return Container(
color: widget.isActive width: 2.5,
? primaryColor.withOpacity(0.8) // 当动画不活跃时,高度固定为3;活跃时使用动画值
: Colors.grey.withOpacity(0.4), height: widget.isActive ? _animation.value : 3,
borderRadius: BorderRadius.circular(1.5), 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 { class SoundBarsWidget extends StatelessWidget {
final bool isActive; final bool isActive;
@ -120,7 +130,7 @@ class SoundBarsWidget extends StatelessWidget {
} }
} }
/// 带控制器集成的声音柱组件 /// 带控制器集成的声音柱组件 (无需改动)
/// 自动从AgentController获取状态 /// 自动从AgentController获取状态
class AgentSoundBarsWidget extends StatelessWidget { class AgentSoundBarsWidget extends StatelessWidget {
final int barCount; final int barCount;

150
lib/modules/home/controllers/home_controller.dart

@ -1,13 +1,11 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'dart:async'; import 'dart:async';
import '../../../data/services/novice_guide_service.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/ble_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 { enum ConnectionStatus {
@ -24,7 +22,7 @@ class HomeController extends GetxController {
final _isFullyConnected = false.obs; final _isFullyConnected = false.obs;
String? _lastConnectedDeviceName; String? _lastConnectedDeviceName;
bool get isFullyConnected => _isFullyConnected.value; bool get isFullyConnected => _isFullyConnected.value;
RxBool get isFullyConnectedRx => _isFullyConnected; RxBool get isFullyConnectedRx => _isFullyConnected;
BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice; BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice;
@ -53,14 +51,9 @@ class HomeController extends GetxController {
final _bluetoothManager = Get.find<BluetoothManager>(); final _bluetoothManager = Get.find<BluetoothManager>();
final _bleManager = Get.find<BleManager>(); final _bleManager = Get.find<BleManager>();
final _storage = GetStorage();
// 会话级别的版本检查标记(静态变量,整个应用会话期间保持) final noviceGuideService = Get.find<NoviceGuideService>();
static bool _hasCheckedVersionInSession = false;
// 版本检查相关常量
static const String _lastVersionCheckKey = 'last_version_check_timestamp';
static const int _versionCheckIntervalHours = 24; // 24小时间隔
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -78,12 +71,6 @@ class HomeController extends GetxController {
void _initWithCachedInfo() { void _initWithCachedInfo() {
_setupStateListeners(); _setupStateListeners();
_checkConnectionStatus(); _checkConnectionStatus();
// 延迟检查版本更新,避免影响页面加载
// 混合检查机制:会话检查 + 24小时间隔检查
Future.delayed(const Duration(seconds: 2), () {
_checkVersionUpdateIfNeeded();
});
} }
void _setupStateListeners() { void _setupStateListeners() {
@ -94,13 +81,14 @@ class HomeController extends GetxController {
update(); update();
}); });
_classicBluetoothWorker = ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) { _classicBluetoothWorker =
ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) { if (device != null) {
_lastConnectedDeviceName = device.name; _lastConnectedDeviceName = device.name;
} }
_checkConnectionStatus(); _checkConnectionStatus();
}); });
_bleConnectionWorker = ever(_bleManager.connectionStateRx, (int state) { _bleConnectionWorker = ever(_bleManager.connectionStateRx, (int state) {
_checkConnectionStatus(); _checkConnectionStatus();
}); });
@ -121,30 +109,30 @@ class HomeController extends GetxController {
void exploreAIFeatures() { void exploreAIFeatures() {
Get.toNamed(Routes.agent); Get.toNamed(Routes.agent);
} }
Future<void> _checkConnectionStatus() async { Future<void> _checkConnectionStatus() async {
try { try {
final currentBleState = await _bleManager.getConnectionState(); final currentBleState = await _bleManager.getConnectionState();
bool hasClassicBluetooth = currentDevice != null; bool hasClassicBluetooth = currentDevice != null;
bool hasBleConnection = currentBleState == 2; bool hasBleConnection = currentBleState == 2;
bool wasConnected = _isFullyConnected.value; bool wasConnected = _isFullyConnected.value;
bool isNowConnected = hasClassicBluetooth && hasBleConnection; bool isNowConnected = hasClassicBluetooth && hasBleConnection;
if (isNowConnected && currentDevice == null) { if (isNowConnected && currentDevice == null) {
isNowConnected = false; isNowConnected = false;
} }
if (_isFullyConnected.value != isNowConnected) { if (_isFullyConnected.value != isNowConnected) {
_isFullyConnected.value = isNowConnected; _isFullyConnected.value = isNowConnected;
_isFullyConnected.refresh(); _isFullyConnected.refresh();
update(); update();
if (!wasConnected && isNowConnected) { if (!wasConnected && isNowConnected) {
await _bleManager.fetchDeviceInfo(); await _bleManager.fetchDeviceInfo();
} }
} }
update(); update();
} catch (e) { } catch (e) {
Logger.e(_tag, '${'checkConnectionStatusFailed'.tr}: $e'); Logger.e(_tag, '${'checkConnectionStatusFailed'.tr}: $e');
@ -165,26 +153,28 @@ class HomeController extends GetxController {
} }
bool get shouldShowDeviceInfo => currentDevice != null; bool get shouldShowDeviceInfo => currentDevice != null;
String getDeviceName() { String getDeviceName() {
if (_isFullyConnected.value && currentDevice == null) { if (_isFullyConnected.value && currentDevice == null) {
_checkConnectionStatus(); _checkConnectionStatus();
} }
final deviceName = currentDevice?.name; final deviceName = currentDevice?.name;
if (deviceName != null && deviceName.isNotEmpty) { if (deviceName != null && deviceName.isNotEmpty) {
return deviceName; return deviceName;
} }
final bleDeviceName = deviceInfo.value?.name; final bleDeviceName = deviceInfo.value?.name;
if (bleDeviceName != null && bleDeviceName.isNotEmpty) { if (bleDeviceName != null && bleDeviceName.isNotEmpty) {
return bleDeviceName; return bleDeviceName;
} }
if (!_isFullyConnected.value && _lastConnectedDeviceName != null && _lastConnectedDeviceName!.isNotEmpty) { if (!_isFullyConnected.value &&
_lastConnectedDeviceName != null &&
_lastConnectedDeviceName!.isNotEmpty) {
return _lastConnectedDeviceName!; return _lastConnectedDeviceName!;
} }
return 'unknownEquipment'.tr; return 'unknownEquipment'.tr;
} }
@ -270,104 +260,4 @@ class HomeController extends GetxController {
? deviceInfo.value!.firmwareVersion ? deviceInfo.value!.firmwareVersion
: 'unknown'.tr; : '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<void> _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');
}
}
} }

107
lib/modules/home/views/home_view.dart

@ -84,11 +84,30 @@ class HomeView extends GetView<HomeController> {
_buildAppBar(isDarkMode), _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), 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), SizedBox(height: 20.h),
// 热门AI体验 // 热门AI体验
@ -624,8 +643,9 @@ class HomeView extends GetView<HomeController> {
Expanded( Expanded(
flex: 1, flex: 1,
child: IntroStepBuilder( child: IntroStepBuilder(
group: 'home',
order: 3, order: 3,
text: 'AI全能助手', text: '太棒了!你已成功唤醒小言,接下来试试让它帮你做件事吧',
onHighlightWidgetTap: () { onHighlightWidgetTap: () {
Intro.of(Get.context!).dispose(); Intro.of(Get.context!).dispose();
controller.exploreAIFeatures(); controller.exploreAIFeatures();
@ -660,62 +680,47 @@ class HomeView extends GetView<HomeController> {
child: Column( child: Column(
children: [ children: [
// 右上卡片 - 同声翻译 // 右上卡片 - 同声翻译
IntroStepBuilder( _buildFeatureCard(
order: 1, title: 'simultaneousInterpretation'.tr, // 同声传译
text: '同声传译', description:
// onWidgetLoad: () { 'simultaneousInterpretationInfo'.tr, // 80+语言实时翻译,出国无忧
// Intro.of(Get.context!).start(); icon: Icons.translate,
// }, gradient: LinearGradient(
builder: (BuildContext context, GlobalKey key) => begin: Alignment.topLeft,
_buildFeatureCard( end: Alignment.bottomRight,
key: key, colors: [
title: 'simultaneousInterpretation'.tr, // 同声传译 const Color(0xFF4CAF50).withOpacity(0.3),
description: const Color(0xFF009688).withOpacity(0.3),
'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),
), ),
onTap: controller.openTranslation,
height: 83.h, // 增加高度
isDarkMode: isDarkMode,
iconColor: const Color(0xFF4CAF50),
), ),
SizedBox(height: 5.h), // 减少间距 SizedBox(height: 5.h), // 减少间距
// 右下卡片 - // 右下卡片 -
// 右下卡片 - 会议助手 // 右下卡片 - 会议助手
IntroStepBuilder( _buildFeatureCard(
order: 2, title: 'conferenceAssistant'.tr, // 商务会议助手
text: '商务会议助手', description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
builder: (BuildContext context, GlobalKey key) => icon: Icons.people,
_buildFeatureCard( gradient: LinearGradient(
key: key, begin: Alignment.topLeft,
title: 'conferenceAssistant'.tr, // 商务会议助手 end: Alignment.bottomRight,
description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译 colors: [
icon: Icons.people, Color(0xFFab47bc).withOpacity(0.3), // 薰衣草紫
gradient: LinearGradient( Color(0xFFec407a).withOpacity(0.3), // 品红
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), // 品红
), ),
onTap: () {
Get.toNamed(Routes.meeting);
},
height: 85.h, // 增加高度
isDarkMode: isDarkMode,
iconColor: const Color(0xFFd81b60), // 品红
), ),
], ],
), ),

211
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 '../../../data/services/version_update_service.dart';
import '../../../modules/common/widgets/version_update_dialog.dart'; import '../../../modules/common/widgets/version_update_dialog.dart';
import '../../../modules/common/widgets/privacy_policy_dialog.dart'; import '../../../modules/common/widgets/privacy_policy_dialog.dart';
import '../../common/webview_page.dart'; import '../../../core/utils/logger.dart';
class SplashController extends GetxController { class SplashController extends GetxController {
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
static const String _tag = 'SplashCtrl';
// 进度条动画控制 // 进度条动画控制
final _loadingProgress = 0.0.obs; final _loadingProgress = 0.0.obs;
double get loadingProgress => _loadingProgress.value; double get loadingProgress => _loadingProgress.value;
@ -34,6 +36,13 @@ class SplashController extends GetxController {
// 主题服务 // 主题服务
final ThemeService _themeService = ThemeService(); final ThemeService _themeService = ThemeService();
// 会话级别的版本检查标记(静态变量,整个应用会话期间保持)
static bool _hasCheckedVersionInSession = false;
// 版本检查相关常量
static const String _lastVersionCheckKey = 'last_version_check_timestamp';
static const int _versionCheckIntervalHours = 24; // 24小时间隔
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -50,10 +59,19 @@ class SplashController extends GetxController {
} }
}); });
//弹出用户追中问题 // 异步处理用户追踪授权,不阻塞主流程
WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) async { _requestTrackingPermissionAsync();
final status = }
await AppTrackingTransparency.requestTrackingAuthorization();
/// 异步请求追踪权限,不阻塞启动流程
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(); _loadAppResources();
} }
/// 智能调度版本检查,在页面跳转后执行,完全不影响启动流程
void _scheduleVersionCheck() {
// 在独立的微任务中执行,避免阻塞主线程
scheduleMicrotask(() async {
try {
Logger.d(_tag, '开始执行版本检查(后台任务)');
_checkVersionUpdateIfNeeded();
} catch (e) {
Logger.e(_tag, '版本检查调度失败: $e');
}
});
}
void _onPrivacyAccepted() { void _onPrivacyAccepted() {
_storage.write('hasAcceptedPrivacy', true); _storage.write('hasAcceptedPrivacy', true);
Get.back(); // 关闭弹窗 Get.back(); // 关闭弹窗
_startNormalFlow(); // 开始正常流程 _startNormalFlow(); // 开始正常流程(已包含版本检查)
} }
void _onPrivacyRejected() { 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秒) // 进度条动画(持续2秒)
void _startLoadingAnimation() { void _startLoadingAnimation() {
const totalDuration = 1500; // 动画总时长增加到1.5秒 const totalDuration = 1500; // 动画总时长增加到1.5秒
@ -122,22 +141,23 @@ class SplashController extends GetxController {
// await _loadImages(); // await _loadImages();
// await _initDatabase(); // await _initDatabase();
// 检查版本更新(在后台进行,不阻塞启动流程)
//_checkVersionUpdate();
_isResourceLoaded.value = true; _isResourceLoaded.value = true;
_checkNavigateCondition(); _checkNavigateCondition();
} catch (e) { } catch (e) {
Get.snackbar('错误', '资源加载失败,请检查网络连接'); Get.snackbar('错误', '资源加载失败,请检查网络连接');
_isResourceLoaded.value = true; // 允许用户继续使用 _isResourceLoaded.value = true; // 允许用户继续使用
print('资源加载错误: $e'); Logger.e(_tag, '资源加载错误: $e');
} }
} }
// 检查跳转条件 // 检查跳转条件
void _checkNavigateCondition() { void _checkNavigateCondition() async {
if (_loadingProgress.value >= 1.0 && _isResourceLoaded.value) { if (_loadingProgress.value >= 1.0 && _isResourceLoaded.value) {
_navigateToLogin(); // 关键改动:先执行页面跳转,并用 await 等待它完成
await _navigateToLogin();
// 页面跳转完全结束后,再安心地安排版本检查
_scheduleVersionCheck();
} }
} }
@ -145,7 +165,7 @@ class SplashController extends GetxController {
Future<void> _navigateToLogin() async { Future<void> _navigateToLogin() async {
bool isLoggedIn = false; bool isLoggedIn = false;
final token = _storage.read('logintoken'); final token = _storage.read('logintoken');
print('getLoginToken:进来了${token}'); Logger.d(_tag, 'getLoginToken:进来了${token}');
if (token != null && token.isNotEmpty) { if (token != null && token.isNotEmpty) {
isLoggedIn = await getLoginToken(); isLoggedIn = await getLoginToken();
} }
@ -159,14 +179,14 @@ class SplashController extends GetxController {
Future<bool> getLoginToken() async { Future<bool> getLoginToken() async {
try { try {
print('getLoginToken:进来了'); Logger.d(_tag, 'getLoginToken:进来了');
//if (User.getname() != "") { //if (User.getname() != "") {
final response = await Api.tokenlogin(); final response = await Api.tokenlogin();
print('getLoginToken: $response'); Logger.d(_tag, 'getLoginToken: $response');
if (response == null) return false; if (response == null) return false;
if (response['user'] != '') { if (response['user'] != '') {
print('getLoginToken:进来了'); Logger.d(_tag, 'getLoginToken:进来了');
// 根据API设计调整 // 根据API设计调整
final user = User.fromJson(response); final user = User.fromJson(response);
@ -178,21 +198,21 @@ class SplashController extends GetxController {
//} //}
return false; return false;
} catch (e) { } catch (e) {
print('Token获取失败: $e'); Logger.e(_tag, 'Token获取失败: $e');
return false; return false;
} }
} }
Future<bool> getAppConfig() async { Future<bool> getAppConfig() async {
try { try {
print('getAppConfig:进来了'); Logger.d(_tag, 'getAppConfig:进来了');
//if (User.getname() != "") { //if (User.getname() != "") {
final response = await Api.getappconfig(); final response = await Api.getappconfig();
print('getAppConfig: $response'); Logger.d(_tag, 'getAppConfig: $response');
if (response == null) return false; if (response == null) return false;
// 根据API设计调整 // 根据API设计调整
final resp = UserGetAppConfigResp.fromJson(response); final resp = UserGetAppConfigResp.fromJson(response);
print('getAppConfig:进来了'); Logger.d(_tag, 'getAppConfig:进来了');
// 存储用户信息(示例) // 存储用户信息(示例)
// _storage.write('ENV', resp.env); // _storage.write('ENV', resp.env);
// _storage.write('AGENTS', resp.agents); // _storage.write('AGENTS', resp.agents);
@ -200,27 +220,116 @@ class SplashController extends GetxController {
AppConfig.initialize(resp); AppConfig.initialize(resp);
return true; return true;
} catch (e) { } catch (e) {
print('getAppConfig 取失败: $e'); Logger.e(_tag, 'getAppConfig 取失败: $e');
return false; return false;
} }
} }
/// 检查版本更新(后台执行,不阻塞启动流程) /// 混合检查机制:判断是否需要进行版本检查
// void _checkVersionUpdate() { /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时
// // 延迟执行版本检查,避免影响启动速度 void _checkVersionUpdateIfNeeded() {
// Timer(const Duration(seconds: 1), () async { try {
// try { final now = DateTime.now();
// final versionService = VersionUpdateService.to; final lastCheckTimestamp = _storage.read(_lastVersionCheckKey);
// final updateInfo = await versionService.checkForUpdate();
bool shouldCheck = false;
// if (updateInfo.hasUpdate) { String reason = '';
// // 显示更新对话框
// await VersionUpdateDialog.show(updateInfo); // 条件1:会话内未检查过
// } if (!_hasCheckedVersionInSession) {
// } catch (e) { shouldCheck = true;
// print('版本检查失败: $e'); 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<void> _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');
}
}
} }

Loading…
Cancel
Save