diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index d26cbc350..0b53e4a8f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -69,7 +69,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = 26 // 更新为26以满足ble_service库的要求 - targetSdk = 34 // 设置为 Android 13 (API 33),以匹配 FOREGROUND_SERVICE_MEDIA_PLAYBACK 权限的要求 + targetSdk = 35 // 设置为 Android 15 (API 35),以满足应用市场要求 versionCode = flutter.versionCode // 对应构建号,自动同步flutter构建号 versionName = flutter.versionName // 对应版本号,自动同步flutter版本号 } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 441e2cafc..a2c7bb84e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,8 @@ + + @@ -70,6 +72,9 @@ android:enableOnBackInvokedCallback="true" tools:replace="android:label"> + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 4cde8e5c9..1087023b6 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -102,6 +102,47 @@ NSAllowsArbitraryLoadsInWebContent + + NSExceptionDomains + + music.126.net + + NSExceptionAllowsInsecureHTTPLoads + + NSExceptionMinimumTLSVersion + TLSv1.0 + NSIncludesSubdomains + + + p1.music.126.net + + NSExceptionAllowsInsecureHTTPLoads + + NSExceptionMinimumTLSVersion + TLSv1.0 + + p2.music.126.net + + NSExceptionAllowsInsecureHTTPLoads + + NSExceptionMinimumTLSVersion + TLSv1.0 + + p3.music.126.net + + NSExceptionAllowsInsecureHTTPLoads + + NSExceptionMinimumTLSVersion + TLSv1.0 + + p4.music.126.net + + NSExceptionAllowsInsecureHTTPLoads + + NSExceptionMinimumTLSVersion + TLSv1.0 + + NSBluetoothAlwaysUsageDescription Bluetooth permission is required to connect and manage audio devices diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 414268fed..4d7ce9fcb 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -156,7 +156,7 @@ class BleManager extends GetxService { /// 设置BLE事件监听 void _setupBleListener() { - Logger.info('lxm--_setupBleListener被调用,开始设置BLE事件监听'); + //Logger.info('lxm--_setupBleListener被调用,开始设置BLE事件监听'); _statusSubscription?.cancel(); _dataSubscription?.cancel(); @@ -394,6 +394,7 @@ class BleManager extends GetxService { Logger.info('lxm-- overallBattery: $overallBattery'); Logger.info('lxm--准备调用_updateDeviceInfoIfNeeded'); + // 更新设备信息 _updateDeviceInfoIfNeeded( batteryLevel: overallBattery, @@ -796,12 +797,12 @@ class BleManager extends GetxService { /// 主动获取电池信息 Future fetchBatteryInfo() async { try { - Logger.info('lxm--fetchBatteryInfo被调用,准备向设备发送获取电池信息指令'); + //Logger.info('lxm--fetchBatteryInfo被调用,准备向设备发送获取电池信息指令'); final result = await _bleService.getBatteryInfo(); - Logger.info('lxm--getBatteryInfo指令发送结果: $result'); + //Logger.info('lxm--getBatteryInfo指令发送结果: $result'); return result; } catch (e) { - Logger.error('lxm--获取电池信息失败: ${e.toString()}'); + //Logger.error('lxm--获取电池信息失败: ${e.toString()}'); return false; } } diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart index 485004914..17d9f0a35 100644 --- a/lib/data/services/novice_guide_service.dart +++ b/lib/data/services/novice_guide_service.dart @@ -14,8 +14,8 @@ class NoviceGuideService extends GetxService { final BleService _bleService = BleService(); StreamSubscription? _statusSubscription; - bool isHomeGuide = true; - bool isAIGuide = true; + bool isHomeGuide = false; + bool isAIGuide = false; RxBool isWakeup = false.obs; diff --git a/lib/data/services/version_update_service.dart b/lib/data/services/version_update_service.dart index 23be2291b..b6822c17f 100644 --- a/lib/data/services/version_update_service.dart +++ b/lib/data/services/version_update_service.dart @@ -353,12 +353,12 @@ class VersionUpdateService extends GetxService { /// 执行更新操作 Future performUpdate(VersionUpdateInfo updateInfo) async { - print('lxm---updateInfo详情:'); - print('lxm---hasUpdate: ${updateInfo.hasUpdate}'); - print('lxm---currentVersion: ${updateInfo.currentVersion}'); - print('lxm---latestVersion: ${updateInfo.latestVersion}'); - print('lxm---updateType: ${updateInfo.updateType}'); - print('lxm---appInfo: ${updateInfo.appInfo?.toJson()}'); + //print('lxm---updateInfo详情:'); + //print('lxm---hasUpdate: ${updateInfo.hasUpdate}'); + //print('lxm---currentVersion: ${updateInfo.currentVersion}'); + //print('lxm---latestVersion: ${updateInfo.latestVersion}'); + //print('lxm---updateType: ${updateInfo.updateType}'); + //print('lxm---appInfo: ${updateInfo.appInfo?.toJson()}'); if (!updateInfo.hasUpdate || updateInfo.appInfo == null) { return false; } diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 4fabc7cc6..78fa49473 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -498,7 +498,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { switch (event.type) { case AgentServiceEventType.recognitionStarted: isListening.value = true; - isTextInputMode.value = false; + //isTextInputMode.value = false; Logger.i(TAG, '订阅代理服务事件识别开始'); // isTextInputMode.refresh(); // 只标记识别开始,不添加消息 @@ -937,7 +937,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { // 注意:这里不需要调用startVoiceInput(),因为原生层已经启动了语音识别 // 我们只需要同步Flutter层的状态 - isTextInputMode.value = false; + //isTextInputMode.value = false; Logger.i(TAG, 'BLE唤醒状态已设置,等待原生层语音识别结果'); } diff --git a/lib/modules/explore/views/explore_view.dart b/lib/modules/explore/views/explore_view.dart index 0f373dbf1..2c9c076ee 100644 --- a/lib/modules/explore/views/explore_view.dart +++ b/lib/modules/explore/views/explore_view.dart @@ -99,7 +99,7 @@ class ExploreView extends GetView { Text( 'explore'.tr, // 发现 style: TextStyle( - fontSize: 24.sp, + fontSize: 20.sp, fontWeight: FontWeight.bold, color: isDarkMode ? Colors.white : Colors.grey[800], ), diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index ebf33cd74..378dfc6ea 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; // 移除了dart:async导入,因为不再使用Timer import '../../../data/services/novice_guide_service.dart'; import '../../../routes/app_routes.dart'; @@ -20,6 +21,7 @@ enum ConnectionStatus { class HomeController extends GetxController { static const String _tag = 'HomeCtrl'; static HomeController get to => Get.find(); + final GetStorage _storage = GetStorage(); final _isFullyConnected = false.obs; String? _lastConnectedDeviceName; @@ -46,13 +48,13 @@ class HomeController extends GetxController { Logger.d(_tag, 'lxm--接收到的DeviceInfo: $newDeviceInfo'); if (newDeviceInfo != null) { - Logger.d(_tag, 'lxm--原始数据 - 左耳电量: ${newDeviceInfo.leftEarBattery}'); - Logger.d(_tag, 'lxm--原始数据 - 右耳电量: ${newDeviceInfo.rightEarBattery}'); - Logger.d(_tag, 'lxm--原始数据 - 充电仓电量: ${newDeviceInfo.caseBattery}'); - Logger.d(_tag, 'lxm--原始数据 - 左耳充电: ${newDeviceInfo.leftEarCharging}'); - Logger.d(_tag, 'lxm--原始数据 - 右耳充电: ${newDeviceInfo.rightEarCharging}'); - Logger.d(_tag, 'lxm--原始数据 - 充电仓充电: ${newDeviceInfo.caseCharging}'); - Logger.d(_tag, 'lxm--原始数据 - 固件版本: ${newDeviceInfo.firmwareVersion}'); + // Logger.d(_tag, 'lxm--原始数据 - 左耳电量: ${newDeviceInfo.leftEarBattery}'); + // Logger.d(_tag, 'lxm--原始数据 - 右耳电量: ${newDeviceInfo.rightEarBattery}'); + // Logger.d(_tag, 'lxm--原始数据 - 充电仓电量: ${newDeviceInfo.caseBattery}'); + // Logger.d(_tag, 'lxm--原始数据 - 左耳充电: ${newDeviceInfo.leftEarCharging}'); + // Logger.d(_tag, 'lxm--原始数据 - 右耳充电: ${newDeviceInfo.rightEarCharging}'); + // Logger.d(_tag, 'lxm--原始数据 - 充电仓充电: ${newDeviceInfo.caseCharging}'); + // Logger.d(_tag, 'lxm--原始数据 - 固件版本: ${newDeviceInfo.firmwareVersion}'); } else { Logger.d(_tag, 'lxm--接收到的DeviceInfo为null'); } @@ -72,13 +74,13 @@ class HomeController extends GetxController { caseCharging.value = newDeviceInfo?.caseCharging ?? false; firmwareVersion.value = newDeviceInfo?.firmwareVersion ?? ''; - Logger.d(_tag, 'lxm--更新后的值 - 左耳电量: ${leftEarBattery.value}'); - Logger.d(_tag, 'lxm--更新后的值 - 右耳电量: ${rightEarBattery.value}'); - Logger.d(_tag, 'lxm--更新后的值 - 充电仓电量: ${caseBattery.value}'); - Logger.d(_tag, 'lxm--更新后的值 - 左耳充电: ${leftEarCharging.value}'); - Logger.d(_tag, 'lxm--更新后的值 - 右耳充电: ${rightEarCharging.value}'); - Logger.d(_tag, 'lxm--更新后的值 - 充电仓充电: ${caseCharging.value}'); - Logger.d(_tag, 'lxm--更新后的值 - 固件版本: ${firmwareVersion.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 左耳电量: ${leftEarBattery.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 右耳电量: ${rightEarBattery.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 充电仓电量: ${caseBattery.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 左耳充电: ${leftEarCharging.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 右耳充电: ${rightEarCharging.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 充电仓充电: ${caseCharging.value}'); + // Logger.d(_tag, 'lxm--更新后的值 - 固件版本: ${firmwareVersion.value}'); } Worker? _deviceInfoWorker; @@ -140,7 +142,16 @@ class HomeController extends GetxController { Future refreshDeviceConnection() async { await _checkConnectionStatus(); - // 移除了立即刷新电池信息的逻辑 + + // 如果设备已连接,主动刷新设备信息 + if (_isFullyConnected.value) { + try { + Logger.d(_tag, '刷新设备信息'); + await _bleManager.fetchDeviceInfo(); + } catch (e) { + Logger.e(_tag, '刷新设备信息失败: $e'); + } + } } void rescanDevices() { @@ -164,6 +175,8 @@ class HomeController extends GetxController { bool wasConnected = _isFullyConnected.value; bool isNowConnected = hasClassicBluetooth && hasBleConnection; + bool isGuideCompleted = _storage.read('home_guide') ?? false; + if (isNowConnected && currentDevice == null) { isNowConnected = false; } @@ -182,9 +195,11 @@ class HomeController extends GetxController { // 移除了连接成功后的电池信息刷新 // 连接成功后检查OTA升级 - Future.delayed(const Duration(seconds: 2), () { - _checkOtaUpgrade(); - }); + if (isGuideCompleted) { + Future.delayed(const Duration(seconds: 2), () { + _checkOtaUpgrade(); + }); + } } // 移除了根据连接状态控制定时器的逻辑 } diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index c626f2e54..a5d7149e8 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -29,7 +29,7 @@ class SettingsView extends GetView { title: Text( 'settings'.tr, // 设置 style: TextStyle( - fontSize: 17.sp, + fontSize: 18.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), diff --git a/lib/modules/translation/views/translation_stats_view.dart b/lib/modules/translation/views/translation_stats_view.dart index 22773ddcb..271bdeb31 100644 --- a/lib/modules/translation/views/translation_stats_view.dart +++ b/lib/modules/translation/views/translation_stats_view.dart @@ -24,6 +24,14 @@ class TranslationStatsView extends GetView { /// 构建应用栏 PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black, + size: 20.sp, + ), + onPressed: () => Get.back(), + ), backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.white, elevation: 0.5, title: Text( @@ -136,20 +144,20 @@ class TranslationStatsView extends GetView { ], ), child: Obx(() => Row( - children: [ - _buildTimeRangeTab('今日', 0, isDarkMode), - _buildTimeRangeTab('本周', 1, isDarkMode), - _buildTimeRangeTab('本月', 2, isDarkMode), - _buildTimeRangeTab('历史', 3, isDarkMode), - ], - )), + children: [ + _buildTimeRangeTab('今日', 0, isDarkMode), + _buildTimeRangeTab('本周', 1, isDarkMode), + _buildTimeRangeTab('本月', 2, isDarkMode), + _buildTimeRangeTab('历史', 3, isDarkMode), + ], + )), ); } /// 构建时间范围标签 Widget _buildTimeRangeTab(String title, int index, bool isDarkMode) { final isSelected = controller.selectedTabIndex.value == index; - + return Expanded( child: GestureDetector( onTap: () => controller.switchTimeRange(index), @@ -215,49 +223,50 @@ class TranslationStatsView extends GetView { ), SizedBox(height: 16.h), Obx(() => Row( - children: [ - Expanded( - child: _buildStatItem( - '翻译次数', - controller.realtimeStats.translationCount.toString(), - Icons.translate, - isDarkMode, - ), - ), - Expanded( - child: _buildStatItem( - '会话时长', - '${controller.realtimeStats.sessionDurationMinutes}分钟', - Icons.timer, - isDarkMode, - ), - ), - ], - )), + children: [ + Expanded( + child: _buildStatItem( + '翻译次数', + controller.realtimeStats.translationCount.toString(), + Icons.translate, + isDarkMode, + ), + ), + Expanded( + child: _buildStatItem( + '会话时长', + '${controller.realtimeStats.sessionDurationMinutes}分钟', + Icons.timer, + isDarkMode, + ), + ), + ], + )), SizedBox(height: 12.h), Obx(() => Row( - children: [ - Expanded( - child: _buildStatItem( - '字符数', - controller.formatNumber( - controller.realtimeStats.sourceCharacters + - controller.realtimeStats.targetCharacters, + children: [ + Expanded( + child: _buildStatItem( + '字符数', + controller.formatNumber( + controller.realtimeStats.sourceCharacters + + controller.realtimeStats.targetCharacters, + ), + Icons.text_fields, + isDarkMode, + ), ), - Icons.text_fields, - isDarkMode, - ), - ), - Expanded( - child: _buildStatItem( - '录音时长', - controller.formatDuration(controller.realtimeStats.recordingDuration), - Icons.mic, - isDarkMode, - ), - ), - ], - )), + Expanded( + child: _buildStatItem( + '录音时长', + controller.formatDuration( + controller.realtimeStats.recordingDuration), + Icons.mic, + isDarkMode, + ), + ), + ], + )), ], ), ); @@ -290,64 +299,68 @@ class TranslationStatsView extends GetView { ), SizedBox(width: 8.w), Obx(() => Text( - '${controller.selectedTimeRange.value}概览', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: isDarkMode ? Colors.white : Colors.black, - ), - )), + '${controller.selectedTimeRange.value}概览', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + )), ], ), SizedBox(height: 16.h), Obx(() => Row( - children: [ - Expanded( - child: _buildStatItem( - '总调用次数', - controller.formatNumber(controller.currentStats.totalApiCalls), - Icons.api, - isDarkMode, - ), - ), - Expanded( - child: _buildStatItem( - '预估成本', - controller.formatCost(controller.currentStats.estimatedCost), - Icons.attach_money, - isDarkMode, - ), - ), - ], - )), + children: [ + Expanded( + child: _buildStatItem( + '总调用次数', + controller + .formatNumber(controller.currentStats.totalApiCalls), + Icons.api, + isDarkMode, + ), + ), + Expanded( + child: _buildStatItem( + '预估成本', + controller + .formatCost(controller.currentStats.estimatedCost), + Icons.attach_money, + isDarkMode, + ), + ), + ], + )), SizedBox(height: 12.h), Obx(() => Row( - children: [ - Expanded( - child: _buildStatItem( - '会话数量', - controller.currentStats.sessionCount.toString(), - Icons.chat, - isDarkMode, - ), - ), - Expanded( - child: _buildStatItem( - '总字符数', - controller.formatNumber(controller.currentStats.totalCharacterCount), - Icons.text_snippet, - isDarkMode, - ), - ), - ], - )), + children: [ + Expanded( + child: _buildStatItem( + '会话数量', + controller.currentStats.sessionCount.toString(), + Icons.chat, + isDarkMode, + ), + ), + Expanded( + child: _buildStatItem( + '总字符数', + controller.formatNumber( + controller.currentStats.totalCharacterCount), + Icons.text_snippet, + isDarkMode, + ), + ), + ], + )), ], ), ); } /// 构建统计项 - Widget _buildStatItem(String label, String value, IconData icon, bool isDarkMode) { + Widget _buildStatItem( + String label, String value, IconData icon, bool isDarkMode) { return Column( children: [ Icon( @@ -414,23 +427,34 @@ class TranslationStatsView extends GetView { ), SizedBox(height: 16.h), Obx(() => Column( - children: [ - _buildApiCallRow('翻译API', controller.currentStats.translationApiCalls, Icons.translate, isDarkMode), - SizedBox(height: 12.h), - _buildApiCallRow('语音识别', controller.currentStats.asrApiCalls, Icons.mic, isDarkMode), - SizedBox(height: 12.h), - _buildApiCallRow('语音合成', controller.currentStats.ttsApiCalls, Icons.volume_up, isDarkMode), - SizedBox(height: 12.h), - _buildApiCallRow('AI摘要', controller.currentStats.aiSummaryApiCalls, Icons.summarize, isDarkMode), - ], - )), + children: [ + _buildApiCallRow( + '翻译API', + controller.currentStats.translationApiCalls, + Icons.translate, + isDarkMode), + SizedBox(height: 12.h), + _buildApiCallRow('语音识别', controller.currentStats.asrApiCalls, + Icons.mic, isDarkMode), + SizedBox(height: 12.h), + _buildApiCallRow('语音合成', controller.currentStats.ttsApiCalls, + Icons.volume_up, isDarkMode), + SizedBox(height: 12.h), + _buildApiCallRow( + 'AI摘要', + controller.currentStats.aiSummaryApiCalls, + Icons.summarize, + isDarkMode), + ], + )), ], ), ); } /// 构建API调用行 - Widget _buildApiCallRow(String label, int count, IconData icon, bool isDarkMode) { + Widget _buildApiCallRow( + String label, int count, IconData icon, bool isDarkMode) { return Row( children: [ Icon( @@ -498,50 +522,54 @@ class TranslationStatsView extends GetView { ), SizedBox(height: 16.h), Obx(() => Column( - children: [ - Row( - children: [ - Expanded( - child: _buildStatItem( - '源文本字符', - controller.formatNumber(controller.currentStats.sourceTextCharacters), - Icons.text_fields, - isDarkMode, - ), - ), - Expanded( - child: _buildStatItem( - 'TTS文本字符', - controller.formatNumber(controller.currentStats.ttsTextCharacters), - Icons.translate, - isDarkMode, - ), - ), - ], - ), - SizedBox(height: 16.h), - Row( children: [ - Expanded( - child: _buildStatItem( - '有效音频时长', - controller.formatDuration(controller.currentStats.effectiveAudioDuration), - Icons.mic, - isDarkMode, - ), + Row( + children: [ + Expanded( + child: _buildStatItem( + '源文本字符', + controller.formatNumber( + controller.currentStats.sourceTextCharacters), + Icons.text_fields, + isDarkMode, + ), + ), + Expanded( + child: _buildStatItem( + 'TTS文本字符', + controller.formatNumber( + controller.currentStats.ttsTextCharacters), + Icons.translate, + isDarkMode, + ), + ), + ], ), - Expanded( - child: _buildStatItem( - '总会话时长', - controller.formatDuration(controller.currentStats.totalSessionDuration), - Icons.volume_up, - isDarkMode, - ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: _buildStatItem( + '有效音频时长', + controller.formatDuration( + controller.currentStats.effectiveAudioDuration), + Icons.mic, + isDarkMode, + ), + ), + Expanded( + child: _buildStatItem( + '总会话时长', + controller.formatDuration( + controller.currentStats.totalSessionDuration), + Icons.volume_up, + isDarkMode, + ), + ), + ], ), ], - ), - ], - )), + )), ], ), ); @@ -585,79 +613,82 @@ class TranslationStatsView extends GetView { ), SizedBox(height: 16.h), Obx(() => Column( - children: [ - // 当前成本 - Container( - padding: EdgeInsets.all(12.w), - decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(8.r), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${controller.selectedTimeRange.value}预估成本', - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.white : Colors.black, - ), - ), - Text( - controller.formatCost(controller.currentStats.estimatedCost), - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), - ), - ], - ), - ), - SizedBox(height: 12.h), - // 预警阈值 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - '日成本预警阈值', - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, + // 当前成本 + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8.r), ), - ), - Text( - controller.formatCost(controller.dailyCostThreshold.value), - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${controller.selectedTimeRange.value}预估成本', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + Text( + controller.formatCost( + controller.currentStats.estimatedCost), + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], ), ), - ], - ), - SizedBox(height: 4.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '月成本预警阈值', - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), + SizedBox(height: 12.h), + // 预警阈值 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '日成本预警阈值', + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + Text( + controller + .formatCost(controller.dailyCostThreshold.value), + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + ], ), - Text( - controller.formatCost(controller.monthlyCostThreshold.value), - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white70 : Colors.black54, - ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '月成本预警阈值', + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + Text( + controller + .formatCost(controller.monthlyCostThreshold.value), + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + ), + ], ), ], - ), - ], - )), + )), ], ), ); @@ -701,31 +732,32 @@ class TranslationStatsView extends GetView { ), SizedBox(height: 16.h), Obx(() => Column( - children: [ - // 最常用语言对 - _buildUsageBehaviorRow( - '最常用语言对', - controller.currentStats.mostUsedLanguagePair, - Icons.language, - isDarkMode, - ), - SizedBox(height: 12.h), - // 最常用翻译模式 - _buildUsageBehaviorRow( - '最常用模式', - controller.currentStats.mostUsedMode, - Icons.settings, - isDarkMode, - ), - ], - )), + children: [ + // 最常用语言对 + _buildUsageBehaviorRow( + '最常用语言对', + controller.currentStats.mostUsedLanguagePair, + Icons.language, + isDarkMode, + ), + SizedBox(height: 12.h), + // 最常用翻译模式 + _buildUsageBehaviorRow( + '最常用模式', + controller.currentStats.mostUsedMode, + Icons.settings, + isDarkMode, + ), + ], + )), ], ), ); } /// 构建使用行为行 - Widget _buildUsageBehaviorRow(String label, String value, IconData icon, bool isDarkMode) { + Widget _buildUsageBehaviorRow( + String label, String value, IconData icon, bool isDarkMode) { return Row( children: [ Icon( diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 864c9b320..7b8c533f2 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -122,6 +122,8 @@ abstract class AppPages { name: Routes.OTA_UPGRADE, page: () => const OtaUpgradeView(), binding: OtaUpgradeBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), ), // GetPage( // name: Routes.profile, @@ -273,6 +275,8 @@ abstract class AppPages { name: Routes.devices, page: () => const DevicesView(), binding: DevicesBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), ), GetPage( name: Routes.ble, diff --git a/local_plugins/ble_service/android/build.gradle.kts b/local_plugins/ble_service/android/build.gradle.kts index 398f8afa7..66a4892b9 100644 --- a/local_plugins/ble_service/android/build.gradle.kts +++ b/local_plugins/ble_service/android/build.gradle.kts @@ -9,11 +9,11 @@ plugins { android { namespace = "com.yunqiinnovation.ble_service" - compileSdk = 34 + compileSdk = 35 defaultConfig { minSdk = 26 - targetSdk = 34 + targetSdk = 35 } compileOptions { @@ -31,4 +31,4 @@ dependencies { // 使用从本地Maven仓库引用的AAR依赖 implementation("com.jieli.audio:jl_audio_decode:2.0.0") implementation("com.jieli.jl_bt_ota:jl_bt_ota:1.9.2") -} \ No newline at end of file +} \ No newline at end of file diff --git a/local_plugins/bytedance_speech/android/build.gradle.kts b/local_plugins/bytedance_speech/android/build.gradle.kts index afd104ba2..402e0b7bd 100644 --- a/local_plugins/bytedance_speech/android/build.gradle.kts +++ b/local_plugins/bytedance_speech/android/build.gradle.kts @@ -29,7 +29,7 @@ android { defaultConfig { minSdk = 26 - targetSdk = 34 + targetSdk = 35 } compileOptions { @@ -53,4 +53,4 @@ dependencies { implementation(project(":speech")) -} \ No newline at end of file +} \ No newline at end of file diff --git a/local_plugins/chat_api/android/build.gradle.kts b/local_plugins/chat_api/android/build.gradle.kts index 0f564abce..44bb954b1 100644 --- a/local_plugins/chat_api/android/build.gradle.kts +++ b/local_plugins/chat_api/android/build.gradle.kts @@ -10,7 +10,7 @@ android { defaultConfig { minSdk = 21 - targetSdk = 33 + targetSdk = 35 } compileOptions { diff --git a/local_plugins/chat_storage/android/build.gradle.kts b/local_plugins/chat_storage/android/build.gradle.kts index 842b98276..f72c52b68 100644 --- a/local_plugins/chat_storage/android/build.gradle.kts +++ b/local_plugins/chat_storage/android/build.gradle.kts @@ -11,12 +11,12 @@ android { namespace = "com.yunqiinnovation.chat_storage" // 目标 SDK 版本 - compileSdk = 33 + compileSdk = 35 defaultConfig { // 最低 SDK 版本 minSdk = 21 - targetSdk = 33 + targetSdk = 35 } // Java 语言级别兼容配置 diff --git a/local_plugins/classic_bluetooth/android/build.gradle.kts b/local_plugins/classic_bluetooth/android/build.gradle.kts index fa40cf9fa..3c6531e8c 100644 --- a/local_plugins/classic_bluetooth/android/build.gradle.kts +++ b/local_plugins/classic_bluetooth/android/build.gradle.kts @@ -16,7 +16,7 @@ android { defaultConfig { // 最低 SDK 版本 minSdk = 21 - targetSdk = 33 + targetSdk = 35 } // Java 语言级别兼容配置 diff --git a/local_plugins/jl_opus/android/build.gradle.kts b/local_plugins/jl_opus/android/build.gradle.kts index 3c0b820e2..9eae80a05 100644 --- a/local_plugins/jl_opus/android/build.gradle.kts +++ b/local_plugins/jl_opus/android/build.gradle.kts @@ -18,12 +18,12 @@ android { namespace = "com.yunqiinnovation.jl_opus" // 目标 SDK 版本 - compileSdk = 33 + compileSdk = 35 defaultConfig { // 最低 SDK 版本 minSdk = 21 - targetSdk = 33 + targetSdk = 35 } // Java 语言级别兼容配置 diff --git a/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift b/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift index 3d13dc7a3..4e6c098ea 100644 --- a/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift +++ b/local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift @@ -16,6 +16,7 @@ public class MusicService: NSObject { private var player: AVPlayer? private var playlist: [[String: Any]] = [] private var currentIndex = 0 + private var currentsongid = Int64(0) public var isPlaying = false private var flutterChannel: FlutterMethodChannel? private var progressTimer: Timer? @@ -34,7 +35,7 @@ public class MusicService: NSObject { private override init() { super.init() setupRemoteCommandCenter() - //setupAudioSession() + setupAudioSession() } public func initialize(serviceUrl: String, userToken: String) { @@ -44,15 +45,21 @@ public class MusicService: NSObject { } - // // MARK: - 音频会话设置 - // private func setupAudioSession() { - // do { - // try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth]) - // try AVAudioSession.sharedInstance().setActive(true) - // } catch { - // print("设置音频会话失败: \(error)") - // } - // } + // MARK: - 音频会话设置 + private func setupAudioSession() { + do { + let audioSession = AVAudioSession.sharedInstance() + try audioSession.setCategory(.playback, mode: .default, options: [.allowAirPlay, .allowBluetooth, .allowBluetoothA2DP]) + try audioSession.setActive(true) + + // 确保媒体控制中心可以接收到我们的应用 + setupRemoteCommandCenter() + + print("音频会话和媒体控制设置成功") + } catch { + print("设置音频会话失败: \(error)") + } + } // MARK: - 基础播放控制 func getCurrentUrlString() -> String? { @@ -153,7 +160,7 @@ public class MusicService: NSObject { // MARK: - 获取歌曲URL - public func getSongUrl(id: Int64, completion: @escaping (Result<[String: Any?], Error>) -> Void) { + public func getSongUrl(id: Int64,curragnt:Int,allagent:Int, completion: @escaping (Result<[String: Any?], Error>) -> Void) { guard !serviceUrl.isEmpty, let url = URL(string: "\(serviceUrl)/api/home/music_url") else { completion(.failure(NSError(domain: "MusicService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Service URL is not configured"]))) return @@ -172,7 +179,12 @@ public class MusicService: NSObject { do { request.httpBody = try JSONSerialization.data(withJSONObject: body, options: []) } catch { - completion(.failure(error)) + if (allagent > 0 && curragnt < allagent){ + getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(error)) + } + return } @@ -181,20 +193,32 @@ public class MusicService: NSObject { let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { print("获取URL请求失败: \(error.localizedDescription)") - completion(.failure(error)) + if (allagent > 0 && curragnt < allagent){ + self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(error)) + } return } guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 print("获取URL失败,HTTP状态码: \(statusCode)") - completion(.failure(NSError(domain: "MusicService", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "获取URL失败,HTTP状态码: \(statusCode)"]))) + if (allagent > 0 && curragnt < allagent){ + self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(NSError(domain: "MusicService", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "获取URL失败,HTTP状态码: \(statusCode)"]))) + } return } guard let data = data else { print("获取URL响应数据为空") - completion(.failure(NSError(domain: "MusicService", code: -2, userInfo: [NSLocalizedDescriptionKey: "响应数据为空"]))) + if (allagent > 0 && curragnt < allagent){ + self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(NSError(domain: "MusicService", code: -2, userInfo: [NSLocalizedDescriptionKey: "响应数据为空"]))) + } return } @@ -202,13 +226,29 @@ public class MusicService: NSObject { if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { let urlData = self.parseUrlFromResponse(jsonResponse: jsonResponse) print("获取URL成功: \(urlData)") - completion(.success(urlData)) + if (!urlData.isEmpty){ + completion(.success(urlData)) + }else{ + if (allagent > 0 && curragnt < allagent){ + self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(NSError(domain: "MusicService", code: -3, userInfo: [NSLocalizedDescriptionKey: "获取连接失败!"]))) + } + } } else { - completion(.failure(NSError(domain: "MusicService", code: -3, userInfo: [NSLocalizedDescriptionKey: "解析JSON响应失败"]))) + if (allagent > 0 && curragnt < allagent){ + self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(NSError(domain: "MusicService", code: -3, userInfo: [NSLocalizedDescriptionKey: "解析JSON响应失败"]))) + } } } catch { print("解析获取URL响应时发生异常: \(error.localizedDescription)") - completion(.failure(error)) + if (allagent > 0 && curragnt < allagent){ + self.getSongUrl(id: id,curragnt: curragnt+1,allagent: allagent,completion: completion) + }else{ + completion(.failure(error)) + } } } @@ -248,7 +288,7 @@ public class MusicService: NSObject { id = 0 currentIdInternal = 0 } - + currentsongid = id print("开始播放play id: \(id)") // 检查 URL 和时间戳 @@ -262,7 +302,7 @@ public class MusicService: NSObject { if needNewUrl { print("URL 不存在或已过期,重新获取 URL") // 调用 getSongUrl 获取新的 URL - getSongUrl(id: id) { [weak self] result in + getSongUrl(id: id,curragnt: 0,allagent: 3) { [weak self] result in DispatchQueue.main.async { switch result { case .success(let urlData): @@ -275,8 +315,11 @@ public class MusicService: NSObject { // 更新 playlist 中对应的歌曲数据 self?.updatePlaylistSong(songId: id, updatedSong: updatedSong) - // 使用新 URL 开始播放 - self?.startPlayback(song: updatedSong, url: newUrl) + if (self?.currentsongid == id) { + // 使用新 URL 开始播放 + self?.startPlayback(song: updatedSong, url: newUrl) + } + } else { print("获取到的 URL 为空") } @@ -343,6 +386,12 @@ public class MusicService: NSObject { currentVolume = 1.0 player?.volume = currentVolume + // 立即设置媒体信息,即使播放器还未准备就绪 + updateNowPlayingInfo(song: song) + + // 确保远程控制中心已设置 + setupRemoteCommandCenter() + print("播放器已创建,等待加载完成...") } @@ -537,20 +586,39 @@ public class MusicService: NSObject { // 异步加载封面图片 if !coverUrl.isEmpty, let url = URL(string: coverUrl) { + print("开始加载封面图片: \(coverUrl)") + DispatchQueue.global(qos: .background).async { - if let data = try? Data(contentsOf: url), - let image = UIImage(data: data) { - DispatchQueue.main.async { - nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image } - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + // 创建带超时的URLRequest + var request = URLRequest(url: url) + request.timeoutInterval = 10.0 + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("图片加载失败: \(error.localizedDescription)") + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + return } - } else { + + guard let data = data, let image = UIImage(data: data) else { + print("图片数据无效或格式不支持") + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + return + } + + print("图片加载成功") DispatchQueue.main.async { + nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image } MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } - } + }.resume() } } else { + print("封面图片URL为空") MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } } @@ -601,20 +669,39 @@ public class MusicService: NSObject { // 异步加载封面图片 if !coverUrl.isEmpty, let url = URL(string: coverUrl) { + print("开始加载封面图片: \(coverUrl)") + DispatchQueue.global(qos: .background).async { - if let data = try? Data(contentsOf: url), - let image = UIImage(data: data) { - DispatchQueue.main.async { - nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image } - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + // 创建带超时的URLRequest + var request = URLRequest(url: url) + request.timeoutInterval = 10.0 + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("图片加载失败: \(error.localizedDescription)") + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + return } - } else { + + guard let data = data, let image = UIImage(data: data) else { + print("图片数据无效或格式不支持") + DispatchQueue.main.async { + MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo + } + return + } + + print("图片加载成功") DispatchQueue.main.async { + nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image } MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } - } + }.resume() } } else { + print("封面图片URL为空") MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo } diff --git a/local_plugins/open_ai_service/android/build.gradle.kts b/local_plugins/open_ai_service/android/build.gradle.kts index f5bbc9337..a93b5d5a7 100644 --- a/local_plugins/open_ai_service/android/build.gradle.kts +++ b/local_plugins/open_ai_service/android/build.gradle.kts @@ -17,7 +17,7 @@ android { defaultConfig { // 最低 SDK 版本 minSdk = 21 - targetSdk = 33 + targetSdk = 35 } // Java 语言级别兼容配置 diff --git a/local_plugins/realtime/android/build.gradle.kts b/local_plugins/realtime/android/build.gradle.kts index b787e4124..10c4dad20 100644 --- a/local_plugins/realtime/android/build.gradle.kts +++ b/local_plugins/realtime/android/build.gradle.kts @@ -10,7 +10,7 @@ android { defaultConfig { minSdk = 21 - targetSdk = 33 + targetSdk = 35 } compileOptions { @@ -44,4 +44,4 @@ dependencies { // OkHttp for WebSocket implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0")) implementation("com.squareup.okhttp3:okhttp") -} \ No newline at end of file +} \ No newline at end of file diff --git a/local_plugins/speech/android/build.gradle.kts b/local_plugins/speech/android/build.gradle.kts index 4e5186a27..532e74759 100644 --- a/local_plugins/speech/android/build.gradle.kts +++ b/local_plugins/speech/android/build.gradle.kts @@ -17,7 +17,7 @@ android { defaultConfig { // 最低 SDK 版本 minSdk = 21 - targetSdk = 33 + targetSdk = 35 } // Java 语言级别兼容配置