diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 127288e55..ebb128fb5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -50,6 +50,8 @@
+
+
diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart
index c7c098a95..97a943a90 100644
--- a/lib/core/bindings/initial_binding.dart
+++ b/lib/core/bindings/initial_binding.dart
@@ -5,6 +5,8 @@ import '../../../data/services/user_portrait.dart';
import '../../../data/services/location_manager.dart';
import '../../../data/services/music_manager.dart';
import '../../../data/services/navigation_manager.dart';
+import '../../../data/services/ast_service.dart';
+import '../../../data/services/speech_impl/azure_ast_service.dart';
import 'package:get/get.dart';
import '../../data/services/meeting/meeting_task_service.dart';
import '../../data/services/meeting/meeting_upload_service.dart';
@@ -25,7 +27,6 @@ class InitialBinding extends Bindings {
void dependencies() {
Logger.warning('InitialBinding dependencies');
- // 语言管理器(需要最先初始化)
// 语言管理器(需要最先初始化)
Get.lazyPut(() => LanguageManager(), fenix: true);
@@ -33,6 +34,9 @@ class InitialBinding extends Bindings {
Get.lazyPut(() => SpeechFactory(), fenix: true);
Get.find().initialize(initialType: SpeechServiceType.azure);
+ // 注册 AST 服务
+ Get.lazyPut(() => AzureAstService(), fenix: true);
+
// 火山翻译服务
Get.lazyPut(() => VolcanoTranslationService(),
fenix: true);
diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart
index 7dca392ef..b4c776ab6 100644
--- a/lib/core/translations/language/en_us.dart
+++ b/lib/core/translations/language/en_us.dart
@@ -487,15 +487,19 @@ const Map enUS = {
// 权限管理页面
'getPermissionStatusFailed': 'Failed to get permission status',
'communicationPermissions': 'Communication Permissions',
+ 'alarmPermissions': 'Alarm Permissions',
'calendarPermissions': 'Calendar Permissions',
'locationPermissions': 'Location Permissions',
'phone': 'Phone',
'sms': 'SMS',
+ 'alarm': 'Alarm',
'calendarWrite': 'Calendar Write',
'calendarFullAccess': 'Calendar Full Access',
'locationInfo': 'Location Info',
'phonePermissionDesc': 'Used for phone call functionality',
'smsPermissionDesc': 'Used for SMS functionality',
+ 'alarmPermissionDesc':
+ 'Used to set exact alarms to ensure reminders trigger on time',
'calendarWritePermissionDesc':
'Used for creating calendar events and reminders',
'calendarFullAccessPermissionDesc':
@@ -512,6 +516,8 @@ const Map enUS = {
'phonePermissionDetailDesc':
'Phone call functionality requires phone permission',
'smsPermissionDetailDesc': 'SMS functionality requires SMS permission',
+ 'alarmPermissionDetailDesc':
+ 'This permission is required to set exact alarms and ensure reminders trigger on time',
'calendarWritePermissionDetailDesc':
'Creating calendar events requires calendar write permission',
'calendarFullAccessPermissionDetailDesc':
diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart
index d73546416..e0757379e 100644
--- a/lib/core/translations/language/zh_cn.dart
+++ b/lib/core/translations/language/zh_cn.dart
@@ -455,15 +455,18 @@ const Map zhCN = {
// 权限管理页面
'getPermissionStatusFailed': '获取权限状态失败',
'communicationPermissions': '通讯权限',
+ 'alarmPermissions': '闹钟权限',
'calendarPermissions': '日历权限',
'locationPermissions': '位置权限',
'phone': '电话',
'sms': '短信',
+ 'alarm': '闹钟',
'calendarWrite': '日历写入',
'calendarFullAccess': '日历完整访问',
'locationInfo': '位置信息',
'phonePermissionDesc': '用于拨打电话功能',
'smsPermissionDesc': '用于发送短信功能',
+ 'alarmPermissionDesc': '用于设置精确闹钟以确保提醒按时触发',
'calendarWritePermissionDesc': '用于创建日历事件和提醒',
'calendarFullAccessPermissionDesc': '用于读取和管理日历事件及提醒',
'locationPermissionDesc': '用于获取当前位置信息',
@@ -477,6 +480,7 @@ const Map zhCN = {
'permissionRequestError': '请求权限时出错',
'phonePermissionDetailDesc': '拨打电话功能需要电话权限',
'smsPermissionDetailDesc': '发送短信功能需要短信权限',
+ 'alarmPermissionDetailDesc': '设置精确闹钟需要此权限,以确保提醒按时触发',
'calendarWritePermissionDetailDesc': '创建日历事件需要日历写入权限',
'calendarFullAccessPermissionDetailDesc': '管理日历事件需要完整日历权限',
'locationPermissionDetailDesc': '获取位置信息需要位置权限',
diff --git a/lib/core/widgets/intro_overlay_builder.dart b/lib/core/widgets/intro_overlay_builder.dart
index bab8412b0..920737ae2 100644
--- a/lib/core/widgets/intro_overlay_builder.dart
+++ b/lib/core/widgets/intro_overlay_builder.dart
@@ -89,22 +89,9 @@ class IntroOverlayBuilder {
}
static Widget _button(String text, Function() onTap) {
- return GestureDetector(
+ return _AnimatedBorderButton(
+ text: text,
onTap: onTap,
- child: Container(
- padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20.r),
- border: Border.all(color: Colors.white),
- ),
- child: Text(
- text,
- style: TextStyle(
- fontSize: 14.sp,
- color: Colors.white,
- ),
- ),
- ),
);
}
}
@@ -166,3 +153,142 @@ class _AnimatedLottieWidgetState extends State<_AnimatedLottieWidget>
);
}
}
+
+// 新增的带边框流动动画的按钮组件
+class _AnimatedBorderButton extends StatefulWidget {
+ final String text;
+ final Function() onTap;
+
+ const _AnimatedBorderButton({
+ required this.text,
+ required this.onTap,
+ });
+
+ @override
+ _AnimatedBorderButtonState createState() => _AnimatedBorderButtonState();
+}
+
+class _AnimatedBorderButtonState extends State<_AnimatedBorderButton>
+ with SingleTickerProviderStateMixin {
+ late AnimationController _animationController;
+ late Animation _animation;
+
+ @override
+ void initState() {
+ super.initState();
+ _animationController = AnimationController(
+ duration: const Duration(seconds: 2),
+ vsync: this,
+ );
+ _animation = Tween(
+ begin: 0.0,
+ end: 1.0,
+ ).animate(_animationController);
+
+ _animationController.repeat();
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: widget.onTap,
+ child: AnimatedBuilder(
+ animation: _animation,
+ builder: (context, child) {
+ return CustomPaint(
+ painter: _FlowingBorderPainter(
+ progress: _animation.value,
+ borderRadius: 20.r,
+ ),
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20.r),
+ border: Border.all(color: Colors.transparent),
+ ),
+ child: Text(
+ widget.text,
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
+
+// 自定义绘制器,用于绘制流动的边框
+class _FlowingBorderPainter extends CustomPainter {
+ final double progress;
+ final double borderRadius;
+
+ _FlowingBorderPainter({
+ required this.progress,
+ required this.borderRadius,
+ });
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ final rect = Rect.fromLTWH(0, 0, size.width, size.height);
+ final rrect = RRect.fromRectAndRadius(rect, Radius.circular(borderRadius));
+
+ // 计算路径总长度
+ final path = Path()..addRRect(rrect);
+ final pathMetrics = path.computeMetrics().first;
+ final totalLength = pathMetrics.length;
+
+ // 流动效果:创建一个渐变的虚线效果
+ final dashLength = totalLength * 0.3; // 虚线长度
+ final startOffset = (progress * totalLength) % totalLength;
+
+ // 绘制流动的边框段
+ for (int i = 0; i < 3; i++) {
+ final offset = (startOffset + i * totalLength / 3) % totalLength;
+ final endOffset = (offset + dashLength) % totalLength;
+
+ Path extractedPath;
+ if (endOffset > offset) {
+ extractedPath = pathMetrics.extractPath(offset, endOffset);
+ } else {
+ // 处理跨越路径起点的情况
+ final path1 = pathMetrics.extractPath(offset, totalLength);
+ final path2 = pathMetrics.extractPath(0, endOffset);
+ extractedPath = Path()
+ ..addPath(path1, Offset.zero)
+ ..addPath(path2, Offset.zero);
+ }
+
+ // 创建渐变效果
+ const gradient = LinearGradient(
+ colors: [
+ Colors.green,
+ Colors.yellow,
+ Colors.red,
+ ],
+ stops: [0.0, 0.5, 1.0],
+ );
+
+ final gradientPaint = Paint()
+ ..shader = gradient.createShader(rect)
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = 1.5;
+
+ canvas.drawPath(extractedPath, gradientPaint);
+ }
+ }
+
+ @override
+ bool shouldRepaint(covariant CustomPainter oldDelegate) {
+ return true;
+ }
+}
diff --git a/lib/data/services/asr_service.dart b/lib/data/services/asr_service.dart
index e6c67563c..4a8b34c2d 100644
--- a/lib/data/services/asr_service.dart
+++ b/lib/data/services/asr_service.dart
@@ -41,11 +41,26 @@ abstract class AsrService {
bool isContinuousRecognitionActive();
/// 开始录音
- Future enableRecord(String filePath);
+ ///
+ /// [filePath] 录音文件路径
+ /// [acceptAudioData] 是否接受音频数据回调,默认为 false
+ Future enableRecord(String filePath, {bool acceptAudioData = false});
+
+ /// 获取音频数据流(如果支持)
+ Stream? getAudioDataStream() => null;
/// 暂停录音
Future pauseRecord();
+ /// 继续录音
+ Future resumeRecord();
+
+ /// 设置音频配置
+ Future setAudioConfig({
+ int sampleRate = 16000,
+ int channels = 1,
+ });
+
// /// 移动文件到新路径
Future moveFile(String sourcePath, String destPath);
@@ -64,12 +79,12 @@ enum RecognitionEventType {
/// 最终识别结果
finalResult,
+ /// 麦识别结果
+ finalResult1,
+
/// 中间识别结果(实时反馈)
intermediateResult,
- /// 音频
- onAudio,
-
/// 会话开始
sessionStarted,
diff --git a/lib/data/services/ast_service.dart b/lib/data/services/ast_service.dart
new file mode 100644
index 000000000..244a7fb37
--- /dev/null
+++ b/lib/data/services/ast_service.dart
@@ -0,0 +1,113 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+/// 语音识别服务接口
+abstract class AstService {
+ /// 支持的语言
+ List get supportedLanguages;
+
+ /// 初始化语音识别服务
+ Future initialize({required List supportedLanguages});
+
+ /// 开始录音
+ Future enableRecord(String filePath);
+
+ ///
+ Future startContinuousTranslation();
+
+ ///
+ Future stopContinuousTranslation();
+
+ /// 返回一个包含识别事件的流
+ Future> recognizeCallback();
+
+ /// 开始录音
+ Future path(String filePath);
+
+ /// 释放资源
+ Future dispose();
+}
+
+// 识别事件类型
+enum RecognitionEventType1 {
+ /// 最终识别结果
+ finalResult,
+
+ /// 中间识别结果(实时反馈)
+ intermediateResult,
+
+ /// 会话开始
+ sessionStarted,
+
+ /// 会话结束
+ sessionStopped,
+
+ /// 识别取消
+ canceled,
+
+ /// 识别错误
+ error,
+}
+
+// 识别事件
+class RecognitionEvent1 {
+ /// 事件类型
+ final RecognitionEventType1 type;
+
+ /// 识别文本(仅在 finalResult 和 intermediateResult 类型中有效)
+ final String text;
+
+ /// 检测到的语言
+ final String detectedLanguage;
+
+ /// 角色
+ final String role;
+
+ /// 原始音频
+ final Uint8List? audio;
+
+ /// 错误信息(仅在 error 和 canceled 类型中有效)
+ final String error;
+
+ RecognitionEvent1({
+ required this.type,
+ this.text = '',
+ this.detectedLanguage = '',
+ this.role = '',
+ this.audio,
+ this.error = '',
+ });
+
+ /// 创建最终结果事件的快捷构造函数
+ factory RecognitionEvent1.finalResult({
+ required String text,
+ String detectedLanguage = '',
+ }) {
+ return RecognitionEvent1(
+ type: RecognitionEventType1.finalResult,
+ text: text,
+ detectedLanguage: detectedLanguage,
+ );
+ }
+
+ /// 创建错误事件的快捷构造函数
+ factory RecognitionEvent1.error(String errorMessage) {
+ return RecognitionEvent1(
+ type: RecognitionEventType1.error,
+ error: errorMessage,
+ );
+ }
+
+ /// 检查是否为最终结果
+ bool get isFinalResult => type == RecognitionEventType1.finalResult;
+
+ /// 检查是否为错误
+ bool get isError =>
+ type == RecognitionEventType1.error ||
+ type == RecognitionEventType1.canceled;
+
+ @override
+ String toString() {
+ return 'RecognitionEvent{type: $type, text: $text, detectedLanguage: $detectedLanguage, error: $error}';
+ }
+}
diff --git a/lib/data/services/audio_service.dart b/lib/data/services/audio_service.dart
new file mode 100644
index 000000000..6dbdd781f
--- /dev/null
+++ b/lib/data/services/audio_service.dart
@@ -0,0 +1,29 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+/// 语音识别服务接口
+abstract class AudioService {
+ /// 开始录音
+ Future enableRecord(String filePath);
+
+ /// 暂停录音
+ Future pauseRecord();
+
+ /// 继续录音
+ Future resumeRecord();
+
+ /// 设置音频配置
+ Future setAudioConfig({
+ int sampleRate = 16000,
+ int channels = 1,
+ });
+
+ // /// 移动文件到新路径
+ Future moveFile(String sourcePath, String destPath);
+
+ // /// 重命名指定路径的音频文件
+ Future renameFile(String filePath, String newName);
+
+ /// 结束录音
+ Future stopRecord(bool isSave);
+}
diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart
index 309dd2ae0..5137faee5 100644
--- a/lib/data/services/speech_impl/azure_asr_service.dart
+++ b/lib/data/services/speech_impl/azure_asr_service.dart
@@ -21,6 +21,10 @@ class AzureAsrService extends GetxService implements AsrService {
static const MethodChannel _channel = MethodChannel('azure_speech/asr');
static const EventChannel _eventChannel =
EventChannel('azure_speech/asr_events');
+ // 音频数据事件通道
+ static const EventChannel _audioDataEventChannel =
+ EventChannel('azure_speech/audio_data_events');
+
// final GetStorage _storage = GetStorage();
bool _isInitialized = false;
late final String _subscriptionKey;
@@ -43,6 +47,11 @@ class AzureAsrService extends GetxService implements AsrService {
String _latestDetectedLanguage = '';
String get latestDetectedLanguage => _latestDetectedLanguage;
+// 音频数据事件订阅
+ StreamSubscription? _audioDataEventSubscription;
+ // 音频数据流控制器
+ StreamController? _audioDataStreamController;
+
// 当前音频源类型
AudioSourceType _audioSourceType = AudioSourceType.microphone;
@@ -72,6 +81,19 @@ class AzureAsrService extends GetxService implements AsrService {
}, onError: _handleRecognitionError);
}
+ /// 设置音频数据事件通道
+ void _setupAudioDataEventChannel() {
+ _audioDataEventSubscription?.cancel();
+ _audioDataEventSubscription =
+ _audioDataEventChannel.receiveBroadcastStream().listen((event) {
+ if (event is Map) {
+ _handleAudioDataEvent(event);
+ }
+ }, onError: (error) {
+ Logger.error('音频数据事件流错误: ${error.toString()}');
+ });
+ }
+
@override
Future initialize({
List? supportedLanguages,
@@ -220,6 +242,36 @@ class AzureAsrService extends GetxService implements AsrService {
return _isContinuousRecognitionActive;
}
+ /// 处理音频数据事件
+ void _handleAudioDataEvent(dynamic event) {
+ if (event is! Map) return;
+
+ final Map eventMap = event;
+ final String eventType = eventMap['type'] as String? ?? '';
+
+ switch (eventType) {
+ case 'audioData':
+ final Uint8List data = eventMap['data'] as Uint8List? ?? Uint8List(0);
+ final double timestamp =
+ (eventMap['timestamp'] as num?)?.toDouble() ?? 0.0;
+ final int size = eventMap['size'] as int? ?? 0;
+ print('接收到音频数据: 大小=${size}字节, 时间戳=${timestamp}');
+ Logger.debug('接收到音频数据: 大小=${size}字节, 时间戳=${timestamp}');
+
+ // 发送到音频数据流
+ _audioDataStreamController?.add(data);
+
+ break;
+ }
+ }
+
+ /// 获取音频数据流
+ Stream? getAudioDataStream() {
+ print("getAudioDataStream");
+ _audioDataStreamController ??= StreamController.broadcast();
+ return _audioDataStreamController?.stream;
+ }
+
/// 处理来自原生端的识别事件
void _handleRecognitionEvent(dynamic event) {
if (event is! Map || _eventStreamController == null) return;
@@ -242,22 +294,27 @@ class AzureAsrService extends GetxService implements AsrService {
detectedLanguage: detectedLanguage,
));
break;
-
- case 'recognizing':
+ case 'result1':
final String text = eventMap['text'] as String? ?? '';
final String detectedLanguage =
eventMap['detectedLanguage'] as String? ?? '';
+ _latestRecognizedText = text;
+ _latestDetectedLanguage = detectedLanguage;
_eventStreamController?.add(RecognitionEvent(
- type: RecognitionEventType.intermediateResult,
+ type: RecognitionEventType.finalResult1,
text: text,
detectedLanguage: detectedLanguage,
));
break;
- case 'onAudio':
- final Uint8List data = eventMap['data'] as Uint8List? ?? Uint8List(0);
+
+ case 'recognizing':
+ final String text = eventMap['text'] as String? ?? '';
+ final String detectedLanguage =
+ eventMap['detectedLanguage'] as String? ?? '';
_eventStreamController?.add(RecognitionEvent(
- type: RecognitionEventType.onAudio,
- audio: data,
+ type: RecognitionEventType.intermediateResult,
+ text: text,
+ detectedLanguage: detectedLanguage,
));
break;
case 'sessionStarted':
@@ -324,6 +381,14 @@ class AzureAsrService extends GetxService implements AsrService {
await _eventSubscription?.cancel();
_eventSubscription = null;
+ // 取消音频数据事件订阅
+ await _audioDataEventSubscription?.cancel();
+ _audioDataEventSubscription = null;
+
+ // 关闭音频数据流
+ await _audioDataStreamController?.close();
+ _audioDataStreamController = null;
+
// 通知原生端释放资源
await _channel.invokeMethod('dispose');
_isInitialized = false;
@@ -364,11 +429,16 @@ class AzureAsrService extends GetxService implements AsrService {
}
@override
- Future enableRecord(String filePath) async {
+ Future enableRecord(String filePath,
+ {bool acceptAudioData = false}) async {
try {
final bool result = await _channel.invokeMethod('enableRecord', {
'filePath': filePath,
+ 'acceptAudioData': acceptAudioData, // 新增参数
});
+ if (acceptAudioData) {
+ _setupAudioDataEventChannel();
+ }
return result;
} catch (e) {
@@ -398,7 +468,30 @@ class AzureAsrService extends GetxService implements AsrService {
return result;
} catch (e) {
- Logger.error('停止录音: ${e.toString()}');
+ Logger.error('暂停录音: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ /// 设置音频配置
+ ///
+ /// [sampleRate] 采样率,默认16000
+ /// [channels] 声道数,默认1(单声道)
+ Future setAudioConfig({
+ int sampleRate = 16000,
+ int channels = 1,
+ }) async {
+ try {
+ final bool result = await _channel.invokeMethod('setAudioConfig', {
+ 'sampleRate': sampleRate,
+ 'channels': channels,
+ });
+
+ Logger.info(
+ '音频配置设置${result ? '成功' : '失败'}: 采样率=$sampleRate, 声道数=$channels');
+ return result;
+ } catch (e) {
+ Logger.error('设置音频配置失败: ${e.toString()}');
rethrow;
}
}
@@ -459,4 +552,16 @@ class AzureAsrService extends GetxService implements AsrService {
rethrow;
}
}
+
+ @override
+ Future resumeRecord() async {
+ try {
+ final bool result = await _channel.invokeMethod('resumeRecord');
+
+ return result;
+ } catch (e) {
+ Logger.error('继续录音: ${e.toString()}');
+ rethrow;
+ }
+ }
}
diff --git a/lib/data/services/speech_impl/azure_ast_service.dart b/lib/data/services/speech_impl/azure_ast_service.dart
new file mode 100644
index 000000000..5b5dc35da
--- /dev/null
+++ b/lib/data/services/speech_impl/azure_ast_service.dart
@@ -0,0 +1,305 @@
+import 'dart:async';
+import '../../../data/models/appconfig.dart';
+import 'package:flutter/services.dart';
+import '../../../core/utils/logger.dart';
+import 'package:get/get.dart';
+import '../ast_service.dart';
+
+/// 音频源类型
+enum AudioSourceType {
+ microphone, // 使用设备麦克风
+ external // 使用外部提供的音频数据
+}
+
+/// Azure 语音识别服务
+///
+/// 该服务提供了通过平台通道与原生 Microsoft Speech SDK 交互的接口
+class AzureAstService extends GetxService implements AstService {
+ static final AzureAstService to = Get.put(AzureAstService());
+ static const MethodChannel _channel = MethodChannel('azure_speech/ast');
+ static const EventChannel _eventChannel =
+ EventChannel('azure_speech/ast_events');
+ // final GetStorage _storage = GetStorage();
+ bool _isInitialized = false;
+ late final String _subscriptionKey;
+ late final String _serviceRegion;
+ late String _baseUrl;
+ final String _endpoint = '/'; // 修改为根路径
+ late final String _accessKey;
+ late final String _secretKey;
+ late final String _region;
+ late final String _service;
+
+ final List _defaultSupportedLanguages = ['zh-CN', 'en-US'];
+ @override
+ List get supportedLanguages => _defaultSupportedLanguages;
+
+ //连续识别相关
+ bool _isContinuousRecognitionActive = false;
+ StreamController? _eventStreamController;
+ StreamSubscription? _eventSubscription;
+
+ // 最新的识别结果
+ String _latestRecognizedText = '';
+ String get latestRecognizedText => _latestRecognizedText;
+
+ // 最新检测到的语言
+ String _latestDetectedLanguage = '';
+ String get latestDetectedLanguage => _latestDetectedLanguage;
+
+ // 当前音频源类型
+ AudioSourceType _audioSourceType = AudioSourceType.microphone;
+
+ AzureAstService() {
+ _loadConfig();
+ }
+
+ /// 设置事件通道
+ void _setupEventChannel() {
+ _eventSubscription?.cancel();
+ _eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) {
+ if (event is Map) {
+ _handleRecognitionEvent(event);
+ }
+ }, onError: _handleRecognitionError);
+ }
+
+ /// 处理来自原生端的识别事件
+ void _handleRecognitionEvent(dynamic event) {
+ if (event is! Map || _eventStreamController == null) return;
+
+ final Map eventMap = event;
+ final String eventType = eventMap['type'] as String? ?? '';
+
+ // 添加日志帮助调试
+
+ switch (eventType) {
+ case 'result':
+ final String text = eventMap['text'] as String? ?? '';
+ final String detectedLanguage =
+ eventMap['detectedLanguage'] as String? ?? '';
+ _latestRecognizedText = text;
+ _latestDetectedLanguage = detectedLanguage;
+ _eventStreamController?.add(RecognitionEvent1(
+ type: RecognitionEventType1.finalResult,
+ text: text,
+ detectedLanguage: detectedLanguage,
+ ));
+ break;
+
+ case 'recognizing':
+ final String text = eventMap['text'] as String? ?? '';
+ final String detectedLanguage =
+ eventMap['detectedLanguage'] as String? ?? '';
+ _eventStreamController?.add(RecognitionEvent1(
+ type: RecognitionEventType1.intermediateResult,
+ text: text,
+ detectedLanguage: detectedLanguage,
+ ));
+ break;
+ case 'sessionStarted':
+ _eventStreamController?.add(RecognitionEvent1(
+ type: RecognitionEventType1.sessionStarted,
+ ));
+ break;
+
+ case 'sessionStopped':
+ _isContinuousRecognitionActive = false;
+ _eventStreamController?.add(RecognitionEvent1(
+ type: RecognitionEventType1.sessionStopped,
+ ));
+ break;
+
+ case 'canceled':
+ _isContinuousRecognitionActive = false;
+ final String reason = eventMap['reason'] as String? ?? '';
+ final String errorDetails = eventMap['errorDetails'] as String? ?? '';
+
+ if (reason.isNotEmpty || errorDetails.isNotEmpty) {
+ Logger.error('识别取消: $reason - ${errorDetails.toString()}');
+ }
+
+ _eventStreamController?.add(RecognitionEvent1(
+ type: RecognitionEventType1.canceled,
+ error: '$reason: $errorDetails',
+ ));
+ break;
+
+ case 'error':
+ final String error = eventMap['message'] as String? ?? '';
+ Logger.error('识别错误: ${error.toString()}');
+ _eventStreamController?.add(RecognitionEvent1(
+ type: RecognitionEventType1.error,
+ error: error,
+ ));
+ break;
+ }
+ }
+
+ /// 处理识别事件流错误
+ void _handleRecognitionError(Object error) {
+ Logger.error('识别事件流错误: ${error.toString()}');
+ _eventStreamController?.addError(error);
+ _cleanupEventStream();
+ }
+
+ /// 清理事件流资源
+ void _cleanupEventStream() {
+ _eventStreamController?.close();
+ _eventStreamController = null;
+ _isContinuousRecognitionActive = false;
+ }
+
+ /// 从环境变量加载配置
+ void _loadConfig() {
+ // final _env = _storage.read("ENV") as Map;
+ _subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? '';
+ _serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? '';
+ _accessKey = AppConfig.env('VOLCANO_TRANSLATION_ACCESS_KEY') ?? '';
+ _secretKey = AppConfig.env('VOLCANO_TRANSLATION_SECRET_KEY') ?? '';
+ _region = AppConfig.env('VOLCANO_TRANSLATION_REGION') ?? 'cn-north-1';
+ _service = 'translate';
+ _baseUrl = 'https://translate.volcengineapi.com';
+ if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
+ throw Exception(
+ '未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
+ }
+ }
+
+ @override
+ Future> recognizeCallback() async {
+ if (!_isInitialized) {
+ await initialize();
+ }
+ try {
+ _eventStreamController = StreamController.broadcast();
+
+ // 开始连续识别
+ final bool result = await _channel.invokeMethod('recognizeCallback');
+ if (!result) {
+ _cleanupEventStream();
+ }
+
+ return _eventStreamController!.stream;
+ } catch (e) {
+ Logger.error('开始连续语音识别失败: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ @override
+ Future enableRecord(String filePath) async {
+ try {
+ final bool result = await _channel.invokeMethod('enableRecord', {
+ 'filePath': filePath,
+ });
+
+ return result;
+ } catch (e) {
+ Logger.error('开始录音: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ @override
+ Future path(String filePath) async {
+ try {
+ final bool result = await _channel.invokeMethod('path', {
+ 'filePath': filePath,
+ });
+
+ return result;
+ } catch (e) {
+ Logger.error('开始录音: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ @override
+ Future startContinuousTranslation() async {
+ try {
+ final bool result =
+ await _channel.invokeMethod('startContinuousTranslation');
+
+ return result;
+ } catch (e) {
+ Logger.error('停止录音: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ @override
+ Future stopContinuousTranslation() async {
+ try {
+ final bool result =
+ await _channel.invokeMethod('stopContinuousTranslation');
+
+ return result;
+ } catch (e) {
+ Logger.error('停止录音: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ @override
+ Future dispose() async {
+ try {
+ final bool result = await _channel.invokeMethod('dispose');
+
+ return;
+ } catch (e) {
+ Logger.error('停止录音: ${e.toString()}');
+ rethrow;
+ }
+ }
+
+ @override
+ Future initialize({
+ List? supportedLanguages,
+ bool useExternalAudio = false,
+ bool useEchoCancellation = false,
+ }) async {
+ try {
+ final List languages =
+ supportedLanguages ?? _defaultSupportedLanguages;
+//底层会初始化前释放
+ // // 检查是否需要重新初始化
+ // if (_isInitialized) {
+ // await dispose();
+ // }
+
+ // 设置音频源类型
+ _audioSourceType = useExternalAudio
+ ? AudioSourceType.external
+ : AudioSourceType.microphone;
+// Future initialize({
+// required String subscriptionKey,
+// required String region,
+// required List supportedLanguages,
+// required String audioSourceType,
+// required String translationAccessKey,
+// required String translationSecretKey,
+// String translationRegion = 'cn-north-1',
+// });
+
+ final bool result = await _channel.invokeMethod('initialize', {
+ 'subscriptionKey': _subscriptionKey,
+ 'region': _serviceRegion,
+ 'supportedLanguages': languages,
+ 'audioSourceType': _audioSourceType.toString().split('.').last,
+ 'translationAccessKey': _accessKey,
+ 'translationSecretKey': _secretKey,
+ 'translationRegion': _region,
+ });
+
+ _isInitialized = result;
+ _setupEventChannel();
+ Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}');
+ return result;
+ } catch (e) {
+ Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
+ _isInitialized = false;
+ rethrow;
+ }
+ }
+}
diff --git a/lib/data/services/speech_impl/volcano_asr_api_service.dart b/lib/data/services/speech_impl/volcano_asr_api_service.dart
index dabf9e6e0..87e0863d8 100644
--- a/lib/data/services/speech_impl/volcano_asr_api_service.dart
+++ b/lib/data/services/speech_impl/volcano_asr_api_service.dart
@@ -813,7 +813,7 @@ class VolcanoAsrApiService implements AsrService {
}
@override
- Future enableRecord(String filePath) {
+ Future enableRecord(String filePath, {bool acceptAudioData = false}) {
// TODO: implement enableRecord
throw UnimplementedError();
}
@@ -865,4 +865,22 @@ class VolcanoAsrApiService implements AsrService {
// TODO: implement startContinuousRecognition
throw UnimplementedError();
}
+
+ @override
+ Future setAudioConfig({int sampleRate = 16000, int channels = 1}) {
+ // TODO: implement setAudioConfig
+ throw UnimplementedError();
+ }
+
+ @override
+ Future resumeRecord() {
+ // TODO: implement resumeRecord
+ throw UnimplementedError();
+ }
+
+ @override
+ Stream? getAudioDataStream() {
+ // TODO: implement getAudioDataStream
+ throw UnimplementedError();
+ }
}
diff --git a/lib/data/services/speech_impl/volcano_asr_service.dart b/lib/data/services/speech_impl/volcano_asr_service.dart
index b5e1493ec..6d090d642 100644
--- a/lib/data/services/speech_impl/volcano_asr_service.dart
+++ b/lib/data/services/speech_impl/volcano_asr_service.dart
@@ -348,7 +348,7 @@ class VolcanoAsrService extends GetxService implements AsrService {
}
@override
- Future enableRecord(String filePath) {
+ Future enableRecord(String filePath, {bool acceptAudioData = false}) {
// TODO: implement enableRecord
throw UnimplementedError();
}
@@ -400,4 +400,22 @@ class VolcanoAsrService extends GetxService implements AsrService {
// TODO: implement startContinuousRecognition
throw UnimplementedError();
}
+
+ @override
+ Future setAudioConfig({int sampleRate = 16000, int channels = 1}) {
+ // TODO: implement setAudioConfig
+ throw UnimplementedError();
+ }
+
+ @override
+ Future resumeRecord() {
+ // TODO: implement resumeRecord
+ throw UnimplementedError();
+ }
+
+ @override
+ Stream? getAudioDataStream() {
+ // TODO: implement getAudioDataStream
+ throw UnimplementedError();
+ }
}
diff --git a/lib/data/services/speech_impl/xunfei_asr_service.dart b/lib/data/services/speech_impl/xunfei_asr_service.dart
index 959f98859..29daf7015 100644
--- a/lib/data/services/speech_impl/xunfei_asr_service.dart
+++ b/lib/data/services/speech_impl/xunfei_asr_service.dart
@@ -272,7 +272,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
@override
- Future enableRecord(String filePath) {
+ Future enableRecord(String filePath, {bool acceptAudioData = false}) {
// TODO: implement enableRecord
throw UnimplementedError();
}
@@ -324,4 +324,22 @@ class XunfeiAsrService extends GetxService implements AsrService {
// TODO: implement startContinuousRecognition
throw UnimplementedError();
}
+
+ @override
+ Future setAudioConfig({int sampleRate = 16000, int channels = 1}) {
+ // TODO: implement setAudioConfig
+ throw UnimplementedError();
+ }
+
+ @override
+ Future resumeRecord() {
+ // TODO: implement resumeRecord
+ throw UnimplementedError();
+ }
+
+ @override
+ Stream? getAudioDataStream() {
+ // TODO: implement getAudioDataStream
+ throw UnimplementedError();
+ }
}
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index 5ffe68ffa..eda548237 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -1070,9 +1070,9 @@ class AgentController extends GetxController {
try {
await AgentService.recognizeCallback();
if (_bluetoothManager.currentDeviceRx.value != null) {
- await AgentService.restoreOriginalAudioState();
+ // await AgentService.restoreOriginalAudioState();
} else {
- await AgentService.disableBluetoothAudio();
+ // await AgentService.disableBluetoothAudio();
}
} catch (e) {
Logger.e(TAG, '启动识别器失败: $e');
diff --git a/lib/modules/login/bindings/login_binding.dart b/lib/modules/login/bindings/login_binding.dart
index 6729681f9..28907a22e 100644
--- a/lib/modules/login/bindings/login_binding.dart
+++ b/lib/modules/login/bindings/login_binding.dart
@@ -4,6 +4,7 @@ import '../controllers/login_controller.dart';
class LoginBinding extends Bindings {
@override
void dependencies() {
- Get.lazyPut(() => LoginController());
+ // 使用 put 而不是 lazyPut,确保控制器立即创建
+ Get.put(LoginController(), permanent: false);
}
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart
index 2740a5b28..3b0c7f590 100644
--- a/lib/modules/login/controllers/login_controller.dart
+++ b/lib/modules/login/controllers/login_controller.dart
@@ -214,15 +214,30 @@ class LoginController extends GetxController {
List get verificationCode => _verificationCode;
final versionService = VersionUpdateService.to;
- // 文本控制器
- TextEditingController contactController = TextEditingController();
- TextEditingController verificationCodeController = TextEditingController();
+ // 文本控制器 - 延迟初始化
+ TextEditingController? _contactController;
+ TextEditingController? _verificationCodeController;
+
+ // Getter 方法,确保控制器存在
+ TextEditingController get contactController {
+ _contactController ??= TextEditingController();
+ return _contactController!;
+ }
+
+ TextEditingController get verificationCodeController {
+ _verificationCodeController ??= TextEditingController();
+ return _verificationCodeController!;
+ }
@override
void onInit() {
super.onInit();
_viewState.value = LoginViewState.welcome;
+ // 初始化控制器
+ _contactController = TextEditingController();
+ _verificationCodeController = TextEditingController();
+
isChinaMainland.value =
Get.deviceLocale?.countryCode?.toUpperCase() == 'CN';
@@ -239,8 +254,21 @@ class LoginController extends GetxController {
@override
void onClose() {
- // contactController.dispose();
- // verificationCodeController.dispose();
+ // 安全地销毁控制器
+ try {
+ _contactController?.dispose();
+ _contactController = null;
+ } catch (e) {
+ Logger().e('销毁 contactController 失败: $e');
+ }
+
+ try {
+ _verificationCodeController?.dispose();
+ _verificationCodeController = null;
+ } catch (e) {
+ Logger().e('销毁 verificationCodeController 失败: $e');
+ }
+
super.onClose();
}
diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart
index 42318e96d..87165d0b0 100644
--- a/lib/modules/login/views/login_view.dart
+++ b/lib/modules/login/views/login_view.dart
@@ -371,27 +371,29 @@ class LoginView extends GetView {
),
SizedBox(width: 12.w),
Expanded(
- child: TextField(
- controller: controller.verificationCodeController,
- keyboardType: TextInputType.number,
- maxLength: 6,
- decoration: InputDecoration(
- hintText: 'verificationCode'.tr, // 验证码
- hintStyle: TextStyle(
+ child: GetBuilder(
+ builder: (controller) => TextField(
+ controller: controller.verificationCodeController,
+ keyboardType: TextInputType.number,
+ maxLength: 6,
+ decoration: InputDecoration(
+ hintText: 'verificationCode'.tr, // 验证码
+ hintStyle: TextStyle(
+ fontSize: 14.sp,
+ color: isDarkMode
+ ? Colors.grey[500]
+ : Colors.black38, // 调整提示文字颜色
+ ),
+ border: InputBorder.none,
+ counterText: '',
+ fillColor: Colors.transparent,
+ filled: true,
+ focusedBorder: InputBorder.none,
+ ),
+ style: TextStyle(
fontSize: 14.sp,
- color: isDarkMode
- ? Colors.grey[500]
- : Colors.black38, // 调整提示文字颜色
+ color: isDarkMode ? Colors.white : Colors.black,
),
- border: InputBorder.none,
- counterText: '',
- fillColor: Colors.transparent,
- filled: true,
- focusedBorder: InputBorder.none,
- ),
- style: TextStyle(
- fontSize: 14.sp,
- color: isDarkMode ? Colors.white : Colors.black,
),
),
),
diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart
index 8cd767ee0..50736fe52 100644
--- a/lib/modules/meeting/controllers/meeting_record_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_record_controller.dart
@@ -60,6 +60,7 @@ class MeetingRecordController extends GetxController
bool _audioSourceType = false;
late TabController tabController;
+ StreamSubscription? _audioDataSubscription;
// 添加波形放大倍数变量
final double waveAmplifyFactor = 100.0; // 增加波动幅度
final RxDouble ursorPosition = 0.0.obs; // 当前播放位置
@@ -123,7 +124,7 @@ class MeetingRecordController extends GetxController
/// Initializes UI controllers and focus nodes
void _initializeComponents() {
- tabController = TabController(length: 2, vsync: this);
+ tabController = TabController(length: 1, vsync: this);
titleEditingController = TextEditingController(text: fileName.value);
titleFocusNode = FocusNode();
titleFocusNode.addListener(_handleTitleFocusChange);
@@ -255,7 +256,22 @@ class MeetingRecordController extends GetxController
final fullFileName = "${fileName.value}_$formattedTime";
// Start audio recording
- await _asrService.enableRecord("${dir.path}/$fullFileName.wav");
+ await _asrService.enableRecord("${dir.path}/$fullFileName.wav",
+ acceptAudioData: true);
+
+ // 监听音频数据流
+ _audioDataSubscription =
+ _asrService.getAudioDataStream()?.listen((audioData) {
+ // 处理音频数据,例如:
+ // 1. 实时音频可视化
+ // 2. 音频质量检测
+ // 3. 发送到其他服务
+ print('接收到音频数据: ${audioData.length} 字节');
+
+ // 处理音频数据的逻辑
+ _processAudioData(audioData);
+ });
+
isRecording.value = true;
// Update state
fileName.value = fullFileName;
@@ -274,9 +290,11 @@ class MeetingRecordController extends GetxController
// _bleManager.openEncoder();
break;
case 1:
+ _asrService.setAudioConfig(sampleRate: 16000, channels: 1);
_bleManager.openDecoder();
break;
case 2:
+ _asrService.setAudioConfig(sampleRate: 16000, channels: 2);
_bleManager.openA2DPDecoder();
break;
}
@@ -291,7 +309,7 @@ class MeetingRecordController extends GetxController
/// Resumes paused recording session
Future _resumeRecording() async {
- await _asrService.startContinuousRecognition(_audioSourceType);
+ await _asrService.resumeRecord();
startTimer();
}
@@ -302,10 +320,6 @@ class MeetingRecordController extends GetxController
case RecognitionEventType.intermediateResult:
intermediateContent.value = event.text;
break;
- case RecognitionEventType.onAudio:
- // 添加新音频数据到波形
- _processAudioData(event.audio!);
- break;
case RecognitionEventType.finalResult:
finalContent.value += _formatTranscript(event);
intermediateContent.value = '';
diff --git a/lib/modules/opus_test/controllers/opus_test_controller.dart b/lib/modules/opus_test/controllers/opus_test_controller.dart
index 6297dee3a..4fad692e9 100644
--- a/lib/modules/opus_test/controllers/opus_test_controller.dart
+++ b/lib/modules/opus_test/controllers/opus_test_controller.dart
@@ -9,6 +9,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:just_audio/just_audio.dart';
import 'package:jl_opus/jl_opus.dart';
import 'package:permission_handler/permission_handler.dart';
+import 'package:share_plus/share_plus.dart'; // 添加分享插件导入
class OpusTestController extends GetxController {
// 选中的文件
@@ -231,7 +232,7 @@ class OpusTestController extends GetxController {
isDecoding.value = false;
return;
}
-
+ print("pcmPath: $pcmPath");
tempPcmPath = pcmPath;
// 将PCM文件转换为WAV文件
@@ -346,6 +347,166 @@ class OpusTestController extends GetxController {
// 数据大小
data.setUint32(40, pcmLength, Endian.little);
}
+
+ /// 删除外部文件
+ Future deleteExternalFile(PlatformFile file) async {
+ try {
+ final fileToDelete = File(file.path!);
+ if (await fileToDelete.exists()) {
+ await fileToDelete.delete();
+ externalFiles.removeWhere((f) => f.path == file.path);
+
+ // 如果删除的是当前选中的文件,清空选择
+ if (selectedFiles.isNotEmpty && selectedFiles.first.path == file.path) {
+ selectedFiles.clear();
+ }
+
+ statusMessage.value = '文件已删除: ${file.name}';
+ } else {
+ statusMessage.value = '文件不存在: ${file.name}';
+ }
+ } catch (e) {
+ statusMessage.value = '删除文件失败: $e';
+ }
+ }
+
+ /// 分享外部文件
+ Future shareExternalFile(PlatformFile file) async {
+ try {
+ if (file.path != null) {
+ final fileToShare = File(file.path!);
+ if (await fileToShare.exists()) {
+ await Share.shareXFiles(
+ [XFile(file.path!)],
+ text: '分享Opus音频文件: ${file.name}',
+ );
+ statusMessage.value = '正在分享文件: ${file.name}';
+ } else {
+ statusMessage.value = '文件不存在: ${file.name}';
+ }
+ }
+ } catch (e) {
+ statusMessage.value = '分享文件失败: $e';
+ }
+ }
+
+ /// 显示文件操作底部弹窗
+ void showFileActionSheet(BuildContext context, PlatformFile file) {
+ showModalBottomSheet(
+ context: context,
+ backgroundColor: Get.isDarkMode ? Colors.grey[900] : Colors.white,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
+ ),
+ builder: (context) => Container(
+ padding: const EdgeInsets.symmetric(vertical: 20),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // 文件信息
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ child: Row(
+ children: [
+ Icon(
+ Icons.audiotrack,
+ color: Get.isDarkMode ? Colors.white70 : Colors.black87,
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ file.name,
+ style: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: Get.isDarkMode ? Colors.white : Colors.black,
+ ),
+ overflow: TextOverflow.ellipsis,
+ ),
+ Text(
+ '${(file.size / 1024).toStringAsFixed(2)} KB',
+ style: TextStyle(
+ fontSize: 14,
+ color: Get.isDarkMode
+ ? Colors.white60
+ : Colors.black54,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ const Divider(),
+ // 分享选项
+ ListTile(
+ leading: const Icon(Icons.share, color: Colors.blue),
+ title: const Text('分享文件'),
+ onTap: () {
+ Navigator.pop(context);
+ shareExternalFile(file);
+ },
+ ),
+ // 删除选项
+ ListTile(
+ leading: const Icon(Icons.delete, color: Colors.red),
+ title: const Text('删除文件', style: TextStyle(color: Colors.red)),
+ onTap: () {
+ Navigator.pop(context);
+ _showDeleteConfirmDialog(context, file);
+ },
+ ),
+ // 取消选项
+ ListTile(
+ leading: const Icon(Icons.cancel),
+ title: const Text('取消'),
+ onTap: () => Navigator.pop(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ /// 显示删除确认对话框
+ void _showDeleteConfirmDialog(BuildContext context, PlatformFile file) {
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ backgroundColor: Get.isDarkMode ? Colors.grey[900] : Colors.white,
+ title: Text(
+ '确认删除',
+ style: TextStyle(
+ color: Get.isDarkMode ? Colors.white : Colors.black,
+ ),
+ ),
+ content: Text(
+ '确定要删除文件 "${file.name}" 吗?\n此操作不可撤销。',
+ style: TextStyle(
+ color: Get.isDarkMode ? Colors.white70 : Colors.black87,
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('取消'),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ deleteExternalFile(file);
+ },
+ style: TextButton.styleFrom(foregroundColor: Colors.red),
+ child: const Text('删除'),
+ ),
+ ],
+ ),
+ );
+ }
}
// PCM音频源
diff --git a/lib/modules/opus_test/views/opus_test_view.dart b/lib/modules/opus_test/views/opus_test_view.dart
index adecc15b6..de02e5138 100644
--- a/lib/modules/opus_test/views/opus_test_view.dart
+++ b/lib/modules/opus_test/views/opus_test_view.dart
@@ -170,6 +170,10 @@ class OpusTestView extends GetView {
onTap: () {
controller.selectExternalFile(file);
},
+ onLongPress: () {
+ // 长按显示操作菜单
+ controller.showFileActionSheet(context, file);
+ },
trailing: Icon(
Icons.arrow_forward,
color:
diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart
index 4dcecf302..496e405e3 100644
--- a/lib/modules/settings/controllers/settings_controller.dart
+++ b/lib/modules/settings/controllers/settings_controller.dart
@@ -192,7 +192,18 @@ class SettingsController extends GetxController {
},
{'type': Permission.bluetooth, 'name': '蓝牙', 'description': '用于连接蓝牙设备'}
]
- }
+ },
+ {
+ 'name': '闹钟权限',
+ 'icon': Icons.alarm,
+ 'permissions': [
+ {
+ 'type': Permission.scheduleExactAlarm,
+ 'name': '闹钟',
+ 'description': '用于设置精确闹钟'
+ }
+ ]
+ },
];
// 检查单个权限状态
@@ -453,7 +464,8 @@ class SettingsController extends GetxController {
Logger().i('更新agent服务语音识别语言: ${supportedLanguages.join(', ')}');
// 调用agent服务更新语言设置
- final success = await AgentService.setSupportedLanguages(supportedLanguages);
+ final success =
+ await AgentService.setSupportedLanguages(supportedLanguages);
if (success) {
Logger().i('agent服务语言设置更新成功');
} else {
diff --git a/lib/modules/settings/views/permissions_view.dart b/lib/modules/settings/views/permissions_view.dart
index c6eebac8c..2b0ceaf80 100644
--- a/lib/modules/settings/views/permissions_view.dart
+++ b/lib/modules/settings/views/permissions_view.dart
@@ -4,6 +4,8 @@ import 'package:permission_handler/permission_handler.dart';
import '../controllers/settings_controller.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui';
+import 'dart:io' show Platform;
+
import '../../../core/utils/permission_util.dart'; // 引入PermissionUtil
class PermissionsView extends GetView {
@@ -37,7 +39,7 @@ class PermissionsView extends GetView {
centerTitle: true,
),
body: FutureBuilder