Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into 唤醒优化

newdev_shunjiawei
fdp 1 year ago
parent
commit
cc70117001
  1. 2
      android/app/build.gradle.kts
  2. 5
      android/app/src/main/AndroidManifest.xml
  3. 41
      ios/Runner/Info.plist
  4. 9
      lib/data/services/ble_manager.dart
  5. 4
      lib/data/services/novice_guide_service.dart
  6. 12
      lib/data/services/version_update_service.dart
  7. 4
      lib/modules/agent/controllers/agent_controller.dart
  8. 2
      lib/modules/explore/views/explore_view.dart
  9. 51
      lib/modules/home/controllers/home_controller.dart
  10. 2
      lib/modules/settings/views/settings_view.dart
  11. 492
      lib/modules/translation/views/translation_stats_view.dart
  12. 4
      lib/routes/app_pages.dart
  13. 6
      local_plugins/ble_service/android/build.gradle.kts
  14. 4
      local_plugins/bytedance_speech/android/build.gradle.kts
  15. 2
      local_plugins/chat_api/android/build.gradle.kts
  16. 4
      local_plugins/chat_storage/android/build.gradle.kts
  17. 2
      local_plugins/classic_bluetooth/android/build.gradle.kts
  18. 4
      local_plugins/jl_opus/android/build.gradle.kts
  19. 159
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift
  20. 2
      local_plugins/open_ai_service/android/build.gradle.kts
  21. 4
      local_plugins/realtime/android/build.gradle.kts
  22. 2
      local_plugins/speech/android/build.gradle.kts

2
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版本号
}

5
android/app/src/main/AndroidManifest.xml

@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<!-- Android 14+ 蓝牙设备连接前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<!-- Android 15+ 数据同步前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
@ -70,6 +72,9 @@
android:enableOnBackInvokedCallback="true"
tools:replace="android:label">
<!-- 支持16KB内存页面大小,满足Android 15要求 -->
<property android:name="android.allow_16kb_memory_page" android:value="true" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>

41
ios/Runner/Info.plist

@ -102,6 +102,47 @@
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<!-- 为网易云音乐图片域名添加例外 -->
<key>NSExceptionDomains</key>
<dict>
<key>music.126.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>p1.music.126.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>p2.music.126.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>p3.music.126.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>p4.music.126.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
</dict>
</dict>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth permission is required to connect and manage audio devices</string>

9
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<bool> 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;
}
}

4
lib/data/services/novice_guide_service.dart

@ -14,8 +14,8 @@ class NoviceGuideService extends GetxService {
final BleService _bleService = BleService();
StreamSubscription<dynamic>? _statusSubscription;
bool isHomeGuide = true;
bool isAIGuide = true;
bool isHomeGuide = false;
bool isAIGuide = false;
RxBool isWakeup = false.obs;

12
lib/data/services/version_update_service.dart

@ -353,12 +353,12 @@ class VersionUpdateService extends GetxService {
/// 执行更新操作
Future<bool> 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;
}

4
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唤醒状态已设置,等待原生层语音识别结果');
}

2
lib/modules/explore/views/explore_view.dart

@ -99,7 +99,7 @@ class ExploreView extends GetView<ExploreController> {
Text(
'explore'.tr, // 发现
style: TextStyle(
fontSize: 24.sp,
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.grey[800],
),

51
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<void> 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();
});
}
}
// 移除了根据连接状态控制定时器的逻辑
}

2
lib/modules/settings/views/settings_view.dart

@ -29,7 +29,7 @@ class SettingsView extends GetView<SettingsController> {
title: Text(
'settings'.tr, // 设置
style: TextStyle(
fontSize: 17.sp,
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),

492
lib/modules/translation/views/translation_stats_view.dart

@ -24,6 +24,14 @@ class TranslationStatsView extends GetView<TranslationStatsController> {
/// 构建应用栏
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<TranslationStatsController> {
],
),
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<TranslationStatsController> {
),
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<TranslationStatsController> {
),
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<TranslationStatsController> {
),
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<TranslationStatsController> {
),
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<TranslationStatsController> {
),
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<TranslationStatsController> {
),
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(

4
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,

6
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")
}
}

4
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"))
}
}

2
local_plugins/chat_api/android/build.gradle.kts

@ -10,7 +10,7 @@ android {
defaultConfig {
minSdk = 21
targetSdk = 33
targetSdk = 35
}
compileOptions {

4
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 语言级别兼容配置

2
local_plugins/classic_bluetooth/android/build.gradle.kts

@ -16,7 +16,7 @@ android {
defaultConfig {
// 最低 SDK 版本
minSdk = 21
targetSdk = 33
targetSdk = 35
}
// Java 语言级别兼容配置

4
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 语言级别兼容配置

159
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
}

2
local_plugins/open_ai_service/android/build.gradle.kts

@ -17,7 +17,7 @@ android {
defaultConfig {
// 最低 SDK 版本
minSdk = 21
targetSdk = 33
targetSdk = 35
}
// Java 语言级别兼容配置

4
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")
}
}

2
local_plugins/speech/android/build.gradle.kts

@ -17,7 +17,7 @@ android {
defaultConfig {
// 最低 SDK 版本
minSdk = 21
targetSdk = 33
targetSdk = 35
}
// Java 语言级别兼容配置

Loading…
Cancel
Save