diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ceba71e9f..7d244c60a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -28,6 +28,10 @@
+
+
+
+
diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart
index 45071ebdd..1ea125f22 100644
--- a/lib/core/translations/language/en_us.dart
+++ b/lib/core/translations/language/en_us.dart
@@ -487,11 +487,18 @@ const Map enUS = {
// 权限管理页面
'getPermissionStatusFailed': 'Failed to get permission status',
'communicationPermissions': 'Communication Permissions',
+ 'bluetoothPermissions': 'Bluetooth Permissions',
+ 'notificationPermissions': 'Notification Permissions',
+ 'storagePermissions': 'Storage Permissions',
+ 'microphonePermissions': 'Microphone Permissions',
'alarmPermissions': 'Alarm Permissions',
'calendarPermissions': 'Calendar Permissions',
'locationPermissions': 'Location Permissions',
'phone': 'Phone',
'sms': 'SMS',
+ 'contacts': 'Contacts',
+ //'bluetooth': 'Bluetooth',
+ 'notification': 'Notification',
'alarm': 'Alarm',
'calendarWrite': 'Calendar Write',
'calendarFullAccess': 'Calendar Full Access',
@@ -521,6 +528,19 @@ const Map enUS = {
'contactsPermissionDesc': 'Used for accessing contact information',
'contactsPermissionDetailDesc':
'Phone call and SMS functionality requires contacts permission to select contacts',
+ 'bluetoothPermissionDesc':
+ 'Used for connecting and managing Bluetooth devices',
+ 'bluetoothPermissionDetailDesc':
+ 'Connecting to Bluetooth headphones and other devices requires Bluetooth permission',
+ 'bluetoothScanPermissionDesc': 'Used for scanning nearby Bluetooth devices',
+ 'bluetoothScanPermissionDetailDesc':
+ 'Scanning for nearby Bluetooth devices requires Bluetooth scan permission',
+ 'bluetoothConnectPermissionDesc': 'Used for connecting to Bluetooth devices',
+ 'bluetoothConnectPermissionDetailDesc':
+ 'Connecting to Bluetooth devices requires Bluetooth connect permission',
+ 'notificationPermissionDesc': 'Used for sending notification messages',
+ 'notificationPermissionDetailDesc':
+ 'App needs notification permission to send you important alerts and messages',
'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 986e8cd14..f52ebd4ab 100644
--- a/lib/core/translations/language/zh_cn.dart
+++ b/lib/core/translations/language/zh_cn.dart
@@ -455,11 +455,18 @@ const Map zhCN = {
// 权限管理页面
'getPermissionStatusFailed': '获取权限状态失败',
'communicationPermissions': '通讯权限',
+ 'bluetoothPermissions': '蓝牙权限',
+ 'notificationPermissions': '通知权限',
+ 'storagePermissions': '存储权限',
+ 'microphonePermissions': '麦克风权限',
'alarmPermissions': '闹钟权限',
'calendarPermissions': '日历权限',
'locationPermissions': '位置权限',
'phone': '电话',
'sms': '短信',
+ 'contacts': '联系人',
+ //'bluetooth': '蓝牙',
+ 'notification': '通知',
'alarm': '闹钟',
'calendarWrite': '日历写入',
'calendarFullAccess': '日历完整访问',
@@ -486,6 +493,14 @@ const Map zhCN = {
'locationPermissionDetailDesc': '获取位置信息需要位置权限',
'contactsPermissionDesc': '用于访问联系人信息',
'contactsPermissionDetailDesc': '拨打电话和发送短信功能需要联系人权限以选择联系人',
+ 'bluetoothPermissionDesc': '用于连接和管理蓝牙设备',
+ 'bluetoothPermissionDetailDesc': '连接蓝牙耳机等设备需要蓝牙权限',
+ 'bluetoothScanPermissionDesc': '用于扫描附近的蓝牙设备',
+ 'bluetoothScanPermissionDetailDesc': '扫描附近蓝牙设备需要蓝牙扫描权限',
+ 'bluetoothConnectPermissionDesc': '用于连接蓝牙设备',
+ 'bluetoothConnectPermissionDetailDesc': '连接蓝牙设备需要蓝牙连接权限',
+ 'notificationPermissionDesc': '用于发送通知消息',
+ 'notificationPermissionDetailDesc': '应用需要通知权限以向您发送重要提醒和消息',
'appNeedsPermissionForFullFunction': '应用需要此权限以提供完整功能',
'permanentDenialText': '您已永久拒绝{permission}权限,请前往应用设置开启',
'goToSettings': '去设置',
diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart
index e761e1a37..4fb1b7343 100644
--- a/lib/data/services/music_manager.dart
+++ b/lib/data/services/music_manager.dart
@@ -22,7 +22,7 @@ class MusicManager extends GetxService {
// Logger.error('无法获取 BuildContext');
// return;
// }
- requestNotificationPermission();
+ //requestNotificationPermission();
Logger.info('初始化权限请求');
}
diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart
index c58b1a312..be154d551 100644
--- a/lib/data/services/novice_guide_service.dart
+++ b/lib/data/services/novice_guide_service.dart
@@ -148,7 +148,7 @@ class NoviceGuideService extends GetxService {
void startAIGuide() async {
if (!isAIGuide) {
- await Future.delayed(const Duration(milliseconds: 200));
+ await Future.delayed(const Duration(milliseconds: 300));
isAIGuide = true;
_storage.write('ai_guide', true);
Intro.of(Get.context!).start(group: 'ai');
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index 009bad72d..e786100ac 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -1060,20 +1060,20 @@ class AgentController extends GetxController {
Future startVoiceInput() async {
if (isListening.value) return;
Logger.i(TAG, '开始语音输入'); // 开始语音输入
- final hasPermission = await PermissionUtil.instance.requestPermission(
- // context: Get.context!,
- permissionType: Permission.microphone,
- permissionName: 'microphonePermission'.tr, // 麦克风
- explanationText: 'voiceTranslationPermissionText'.tr, // 语音识别与翻译功能需要访问麦克风。
- permanentDenialText:
- 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
- settingsButtonText: 'goToSettings'.tr, // 去设置
- cancelButtonText: 'cancel'.tr, // 取消
- );
- if (!hasPermission) {
- isTextInputMode.value = true; // 如果没有权限,切回文本模式
- return;
- }
+ // final hasPermission = await PermissionUtil.instance.requestPermission(
+ // // context: Get.context!,
+ // permissionType: Permission.microphone,
+ // permissionName: 'microphonePermission'.tr, // 麦克风
+ // explanationText: 'voiceTranslationPermissionText'.tr, // 语音识别与翻译功能需要访问麦克风。
+ // permanentDenialText:
+ // 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
+ // settingsButtonText: 'goToSettings'.tr, // 去设置
+ // cancelButtonText: 'cancel'.tr, // 取消
+ // );
+ // if (!hasPermission) {
+ // isTextInputMode.value = true; // 如果没有权限,切回文本模式
+ // return;
+ // }
try {
await AgentService.recognizeCallback();
diff --git a/lib/modules/meeting/controllers/meeting_home_controller.dart b/lib/modules/meeting/controllers/meeting_home_controller.dart
index 1b5af2369..e994d5025 100644
--- a/lib/modules/meeting/controllers/meeting_home_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_home_controller.dart
@@ -1,5 +1,6 @@
import 'dart:io';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
@@ -88,6 +89,24 @@ class MeetingHomeController extends GetxController {
// 请求存储权限
void _requestStoragePermission() async {
+ // 只在Android 10及以下版本请求存储权限
+ if (Platform.isAndroid) {
+ try {
+ final deviceInfo = DeviceInfoPlugin();
+ final androidInfo = await deviceInfo.androidInfo;
+ final sdkVersion = androidInfo.version.sdkInt;
+
+ // Android 11+ (API 30+) 不需要存储权限
+ if (sdkVersion >= 30) {
+ Logger.d("Permission", "Android 11+版本,无需请求存储权限");
+ return;
+ }
+ } catch (e) {
+ Logger.e("Permission", "获取Android版本信息失败: $e");
+ // 如果获取版本失败,为了兼容性还是请求权限
+ }
+ }
+
PermissionUtil.instance
.requestPermission(
permissionType: Permission.storage,
diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart
index 11177bb1b..b30651e28 100644
--- a/lib/modules/meeting/controllers/meeting_record_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_record_controller.dart
@@ -23,7 +23,7 @@ enum TimerStatus {
/// Controller for meeting recording screen
/// Handles recording state, audio processing, transcription, and UI interactions
class MeetingRecordController extends GetxController
- with SingleGetTickerProviderMixin {
+ with GetSingleTickerProviderStateMixin {
// Services
final AsrService _asrService = Get.find();
final BleManager _bleManager = Get.find();
@@ -57,8 +57,7 @@ class MeetingRecordController extends GetxController
Timer? _timer;
int _elapsedMilliseconds = 0;
int? _lastStartTime;
- bool _audioSourceType = false;
- late TabController tabController;
+ // 移除了未使用的变量和TabController
StreamSubscription? _audioDataSubscription;
// 添加波形放大倍数变量
@@ -67,6 +66,10 @@ class MeetingRecordController extends GetxController
final RxInt _currentPosition = 0.obs; // 原始位置索引
int maxPoints = 500; // 最大显示点数(根据屏幕宽度优化)
int sampleRate = 16000; // 假设音频采样率(根据实际情况调整)
+
+ // 波形流动动画控制器
+ late AnimationController waveformAnimationController;
+ late Animation waveformAnimation;
// 设置最大点数(根据屏幕宽度)
void setMaxPoints(double screenWidth) {
maxPoints = screenWidth ~/ 2; // 每2像素一个数据点
@@ -77,12 +80,14 @@ class MeetingRecordController extends GetxController
super.onInit();
_initializeComponents();
_setupFromArguments();
- generateWaveData();
+ _initializeWaveformAnimation();
+ //generateWaveData();
}
@override
void onClose() {
_cleanupResources();
+ waveformAnimationController.dispose();
super.onClose();
}
@@ -111,12 +116,36 @@ class MeetingRecordController extends GetxController
/// Initializes UI controllers and focus nodes
void _initializeComponents() {
- tabController = TabController(length: 1, vsync: this);
titleEditingController = TextEditingController(text: fileName.value);
titleFocusNode = FocusNode();
titleFocusNode.addListener(_handleTitleFocusChange);
}
+ /// 初始化波形流动动画
+ void _initializeWaveformAnimation() {
+ // 设置动画持续时间,控制流动速度
+ waveformAnimationController = AnimationController(
+ duration: const Duration(milliseconds: 100), // 更快的流动速度
+ vsync: this,
+ );
+
+ waveformAnimation = Tween(
+ begin: 0.0,
+ end: 1.0,
+ ).animate(CurvedAnimation(
+ parent: waveformAnimationController,
+ curve: Curves.linear, // 线性动画确保匀速流动
+ ));
+
+ // 监听动画完成,实现连续循环
+ waveformAnimationController.addStatusListener((status) {
+ if (status == AnimationStatus.completed && isRecording.value) {
+ waveformAnimationController.reset();
+ waveformAnimationController.forward();
+ }
+ });
+ }
+
/// Sets up initial state from navigation arguments
void _setupFromArguments() {
final Map args = Get.arguments ?? {};
@@ -126,15 +155,12 @@ class MeetingRecordController extends GetxController
switch (audioType.value) {
case 0:
fileName.value = "现场录音";
- _audioSourceType = false;
break;
case 1:
fileName.value = "音、视频录音";
- _audioSourceType = true;
break;
case 2:
fileName.value = "通话录音";
- _audioSourceType = true;
break;
}
newName.value = fileName.value;
@@ -145,6 +171,8 @@ class MeetingRecordController extends GetxController
_timer?.cancel();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
+ _audioDataSubscription?.cancel();
+ _audioDataSubscription = null;
titleEditingController.dispose();
titleFocusNode.dispose();
if (audioType.value != 0) {
@@ -242,7 +270,7 @@ class MeetingRecordController extends GetxController
// 1. 实时音频可视化
// 2. 音频质量检测
// 3. 发送到其他服务
- print('接收到音频数据: ${audioData.length} 字节');
+ Logger.d("AudioData", "接收到音频数据: ${audioData.length} 字节");
// 处理音频数据的逻辑
_processAudioData(audioData);
@@ -254,6 +282,9 @@ class MeetingRecordController extends GetxController
newName.value = fullFileName;
titleEditingController.text = fullFileName;
+ // 启动波形动画
+ _startWaveformAnimation();
+
// Start hardware components
_startHardwareServices();
startTimer();
@@ -279,31 +310,17 @@ class MeetingRecordController extends GetxController
/// Pauses current recording session
Future _pauseRecording() async {
await _asrService.pauseRecord();
+ _stopWaveformAnimation(); // 暂停时停止动画
pauseTimer();
}
/// Resumes paused recording session
Future _resumeRecording() async {
await _asrService.resumeRecord();
-
+ _startWaveformAnimation(); // 恢复时启动动画
startTimer();
}
- /// Handles speech recognition events
- void _handleRecognitionEvent(RecognitionEvent event) {
- switch (event.type) {
- case RecognitionEventType.intermediateResult:
- intermediateContent.value = event.text;
- break;
- case RecognitionEventType.finalResult:
- finalContent.value += _formatTranscript(event);
- intermediateContent.value = '';
- break;
- default:
- break;
- }
- }
-
/// 处理原始Uint8List音频数据
void _processAudioData(Uint8List audioData) {
// 将Uint8List转换为Float32List(PCM16 -> 浮点)
@@ -360,70 +377,55 @@ class MeetingRecordController extends GetxController
return _currentPosition.value / waveData.length;
}
- /// 添加处理后的音频数据
+ /// 添加处理后的音频数据 - 优化流动效果
void _addAudioData(Float32List newAudio) {
- // 1. 降采样:只保留部分数据点
- final downsampled = _downsampleAudio(newAudio);
+ if (!isRecording.value) return;
- waveData.addAll(downsampled);
+ // 1. 降采样处理
+ final downsampled = _downsampleAudio(newAudio);
+ if (downsampled.isEmpty) return;
- // 3. 更新光标位置到最新点
- _currentPosition.value = waveData.length;
+ // 2. 逐个添加数据点,确保平滑流动
+ for (final sample in downsampled) {
+ waveData.add(sample);
- // 4. 限制数据长度,移除旧数据
- if (waveData.length > maxPoints * 2) {
- waveData.removeRange(0, waveData.length - maxPoints);
- _currentPosition.value = waveData.length;
+ //维护合理的数据长度 - 保持滑动窗口效果
+ if (waveData.length > maxPoints) {
+ waveData.removeAt(0); // 移除最旧的数据
+ }
}
+
+ // 3. 确保动画持续运行
+ _ensureAnimationRunning();
+
+ // 4. 更新位置信息
+ _currentPosition.value = waveData.length;
}
- /// Formats transcript with speaker identification
- String _formatTranscript(RecognitionEvent event) {
- final buffer = StringBuffer();
-
- if (finalContent.value.isNotEmpty) {
- if (event.role.isNotEmpty) {
- // Add line break before new speaker
- if (_isPunctuation(event.text[0])) {
- buffer.write('${event.text[0]}\n');
- buffer.write('说话人${event.role}:');
- buffer.write(event.text.substring(1));
- } else {
- buffer.write('\n说话人${event.role}:');
- buffer.write(event.text);
- }
- } else {
- buffer.write(event.text);
- }
- } else {
- buffer.write('说话人${event.role}:${event.text}');
+ /// 启动波形流动动画
+ void _startWaveformAnimation() {
+ if (isRecording.value && !waveformAnimationController.isAnimating) {
+ waveformAnimationController.forward();
}
+ }
- return buffer.toString();
+ /// 停止波形动画
+ void _stopWaveformAnimation() {
+ waveformAnimationController.stop();
+ waveformAnimationController.reset();
}
- /// Checks if character is Chinese/English punctuation
- bool _isPunctuation(String char) {
- const punctuations = {
- ',',
- '。',
- '!',
- '?',
- ';',
- ':',
- ',',
- '.',
- ':',
- ';',
- '!',
- '?'
- };
- return punctuations.contains(char);
+ /// 确保动画持续运行
+ void _ensureAnimationRunning() {
+ if (isRecording.value && !waveformAnimationController.isAnimating) {
+ waveformAnimationController.forward();
+ }
}
/// Saves recording and cleans up resources
Future saveRecording() async {
resetTimer();
+ _stopWaveformAnimation(); // 停止波形动画
if (audioType.value != 0) {
_bleManager.closeCodec();
}
@@ -436,11 +438,11 @@ class MeetingRecordController extends GetxController
final dir = Directory("${appDir.path}/MeetingAudio/");
if (!await dir.exists()) await dir.create(recursive: true);
if (isMarked.value) {
- final Newdir = Directory("${appDir.path}/DestAudio/");
- if (!await Newdir.exists()) await Newdir.create(recursive: true);
+ final newDir = Directory("${appDir.path}/DestAudio/");
+ if (!await newDir.exists()) await newDir.create(recursive: true);
await _asrService.moveFile(
"${dir.path}${fileName.value}.wav",
- "${Newdir.path}${fileName.value}.wav",
+ "${newDir.path}${fileName.value}.wav",
);
} else {
await _asrService.renameFile(
diff --git a/lib/modules/meeting/views/meeting_record_view.dart b/lib/modules/meeting/views/meeting_record_view.dart
index 4de8a3320..3784e9673 100644
--- a/lib/modules/meeting/views/meeting_record_view.dart
+++ b/lib/modules/meeting/views/meeting_record_view.dart
@@ -3,7 +3,6 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
-import 'package:lottie/lottie.dart';
import '../controllers/meeting_record_controller.dart';
import '../../../core/theme/app_colors.dart';
@@ -14,17 +13,22 @@ class MeetingRecordView extends GetView {
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
- return WillPopScope(
- onWillPop: () async {
- // 在返回时执行保存操作
- controller.saveTitleChange();
- await controller.saveRecording();
- return true; // 允许退出
+ return PopScope(
+ onPopInvokedWithResult: (didPop, result) async {
+ if (didPop) {
+ // 在返回时执行保存操作
+ controller.saveTitleChange();
+ //await controller.saveRecording();
+ }
},
child: Scaffold(
appBar: AppBar(
leading: IconButton(
- icon: const Icon(Icons.arrow_back),
+ icon: Icon(
+ Icons.arrow_back_ios,
+ color: isDarkMode ? Colors.white : Colors.black,
+ size: 20.sp,
+ ),
onPressed: () async {
// 在返回按钮点击时执行保存操作
controller.saveTitleChange();
@@ -48,7 +52,7 @@ class MeetingRecordView extends GetView {
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
- hintText: '输入文件名',
+ hintText: 'enterFileName'.tr,
hintStyle: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
),
@@ -88,24 +92,9 @@ class MeetingRecordView extends GetView {
),
body: Column(
children: [
- TabBar(
- controller: controller.tabController,
- indicatorColor: Colors.red,
- labelColor: isDarkMode ? Colors.white : Colors.black,
- unselectedLabelColor: Colors.grey,
- tabs: const [
- Tab(text: '音频'),
- // Tab(text: '文本'),
- ],
- ),
+ // 简化为单一音频界面,移除不必要的TabBar
Expanded(
- child: TabBarView(
- controller: controller.tabController,
- children: [
- _buildAudioTab(context, isDarkMode),
- // _buildTextTab(context, isDarkMode),
- ],
- ),
+ child: _buildAudioTab(context, isDarkMode),
),
],
),
@@ -114,339 +103,574 @@ class MeetingRecordView extends GetView {
}
Widget _buildAudioTab(BuildContext context, bool isDarkMode) {
- return Obx(() {
- final screenWidth = MediaQuery.of(context).size.width;
- // 初始化最大点数
- if (controller.maxPoints == 0) {
- controller.setMaxPoints(screenWidth);
- }
- final cursorPos = controller.cursorPositionValue;
- return Column(
- children: [
- const SizedBox(height: 24),
- SizedBox(
- height: 120,
- child: Stack(
- alignment: Alignment.center,
- children: [
- // 波形绘制
- CustomPaint(
- painter: WaveformPainter(
- controller.waveData,
- cursorPosition: cursorPos,
- // color: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
- ),
- size: Size(screenWidth, 120),
- ),
- // 光标指示器
- Positioned(
- left: MediaQuery.of(context).size.width * cursorPos,
- child: Container(
- width: 2,
- height: 120,
- color: Colors.red,
- ),
- ),
- ],
- ),
- ),
- const SizedBox(height: 16),
- Text(
- controller.formatTime(controller.seconds.value),
- style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
- ),
- const Spacer(),
- _buildBottomBar(context, isDarkMode),
- const SizedBox(height: 32),
- ],
- );
- });
- }
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ // 使用LayoutBuilder获取准确的屏幕宽度
+ final screenWidth = constraints.maxWidth;
+
+ // 初始化最大点数
+ if (controller.maxPoints == 0) {
+ controller.setMaxPoints(screenWidth);
+ }
- Widget _buildTextTab(BuildContext context, bool isDarkMode) {
- return Obx(() => Column(
+ return Column(
children: [
- const SizedBox(height: 48),
- Center(
- child: Column(
- children: [
- const Icon(Icons.graphic_eq, color: Colors.red, size: 32),
- const SizedBox(height: 8),
- Text(
- controller.formatTime(controller.seconds.value),
- style: const TextStyle(
- fontSize: 36, fontWeight: FontWeight.bold),
+ SizedBox(height: 24.h),
+ // 专业级实时流动波形显示区域
+ Container(
+ height: 120.h,
+ margin: EdgeInsets.symmetric(horizontal: 16.w),
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: isDarkMode
+ ? [
+ const Color(0xFF1A1A1A),
+ const Color(0xFF2A2A2A),
+ const Color(0xFF1A1A1A),
+ ]
+ : [
+ const Color(0xFFF8F9FA),
+ const Color(0xFFFFFFFF),
+ const Color(0xFFF8F9FA),
+ ],
+ ),
+ borderRadius: BorderRadius.circular(12.r),
+ border: Border.all(
+ color: isDarkMode
+ ? Colors.white.withValues(alpha: 0.1)
+ : Colors.black.withValues(alpha: 0.05),
+ width: 1,
+ ),
+ boxShadow: [
+ BoxShadow(
+ color: isDarkMode
+ ? Colors.black.withValues(alpha: 0.3)
+ : Colors.black.withValues(alpha: 0.08),
+ blurRadius: 8,
+ offset: const Offset(0, 2),
),
],
),
- ),
- const SizedBox(height: 24),
- // 新增:转文本提示区域
- if (!controller.showTextContent.value) ...[
- const Text('无文本',
- style: TextStyle(
- color: Colors.grey,
- fontSize: 16,
- )),
- const SizedBox(height: 16),
- ElevatedButton(
- onPressed: controller.toggleTextDisplay,
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.red,
- foregroundColor: Colors.white,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- padding: const EdgeInsets.symmetric(
- horizontal: 24,
- vertical: 8,
- ),
- ),
- child: const Text('转文本'),
- ),
- ],
- // 文本内容区域(转文本后显示)
- if (controller.showTextContent.value)
- Expanded(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 24),
- child: SingleChildScrollView(
- child: Text(
- '${controller.finalContent.value}${controller.intermediateContent.value}',
- style: const TextStyle(fontSize: 16),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(12.r),
+ child: Stack(
+ children: [
+ // 实时流动波形绘制组件
+ _WaveformWidget(
+ controller: controller,
+ isDarkMode: isDarkMode,
+ size: Size(screenWidth - 32.w, 120.h),
),
- ),
+
+ // 录音状态指示器
+ Obx(() {
+ if (controller.timerStatus.value == TimerStatus.running) {
+ return Positioned(
+ top: 8.h,
+ right: 8.w,
+ child: Container(
+ padding: EdgeInsets.symmetric(
+ horizontal: 6.w,
+ vertical: 2.h,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.red.withValues(alpha: 0.9),
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ width: 4.w,
+ height: 4.h,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ shape: BoxShape.circle,
+ ),
+ ),
+ SizedBox(width: 3.w),
+ Text(
+ 'REC',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 8.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+ return const SizedBox.shrink();
+ }),
+ ],
),
),
+ ),
+ SizedBox(height: 24.h),
+ // 时间显示 - 只包装需要响应式更新的Text widget
+ Obx(() => Text(
+ controller.formatTime(controller.seconds.value),
+ style: TextStyle(
+ fontSize: 48.sp,
+ fontWeight: FontWeight.bold,
+ color: isDarkMode ? Colors.white : Colors.black87,
+ ),
+ )),
const Spacer(),
_buildBottomBar(context, isDarkMode),
- const SizedBox(height: 32),
+ SizedBox(height: 32.h),
],
- ));
+ );
+ },
+ );
}
// 底部操作栏
Widget _buildBottomBar(BuildContext context, bool isDarkMode) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- // 收藏按钮
- GestureDetector(
- onTap: controller.toggleMarked,
- child: Obx(() => CircleAvatar(
- radius: 32,
- backgroundColor: Colors.white,
- child: Icon(
+ return Padding(
+ padding: EdgeInsets.symmetric(horizontal: 20.w),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ // 收藏按钮
+ _buildActionButton(
+ onTap: controller.toggleMarked,
+ child: Obx(() => Icon(
controller.isMarked.value ? Icons.star : Icons.star_border,
- size: 36,
+ size: 36.sp,
color:
controller.isMarked.value ? Colors.orange : Colors.grey,
- ),
- )),
- ),
- // 录音按钮
- GestureDetector(
- onTap: controller.toggleRecording,
- child: Obx(() => CircleAvatar(
- radius: 40,
- backgroundColor: Colors.white,
- child: Icon(
+ )),
+ radius: 32.w,
+ tooltip: controller.isMarked.value
+ ? 'unmarkFavorite'.tr
+ : 'markFavorite'.tr,
+ ),
+ // 录音按钮
+ _buildActionButton(
+ onTap: controller.toggleRecording,
+ child: Obx(() => Icon(
controller.timerStatus.value == TimerStatus.running
? Icons.pause
: Icons.play_arrow,
- size: 44,
+ size: 44.sp,
color: Colors.red,
- ),
- )),
- ),
- // 完成按钮
- GestureDetector(
- onTap: () => _bottomSheet(context, isDarkMode), // 改为匿名函数
- child: const CircleAvatar(
- radius: 32,
- backgroundColor: Colors.white,
+ )),
+ radius: 40.w,
+ tooltip: controller.timerStatus.value == TimerStatus.running
+ ? 'pauseRecording'.tr
+ : 'startRecording'.tr,
+ ),
+ // 完成按钮
+ _buildActionButton(
+ onTap: () => _bottomSheet(context, isDarkMode),
child: Icon(
Icons.check,
- size: 36,
+ size: 36.sp,
color: Colors.green,
),
+ radius: 32.w,
+ tooltip: 'finishRecording'.tr,
+ ),
+ ],
+ ),
+ );
+ }
+
+ // 统一的操作按钮组件
+ Widget _buildActionButton({
+ required VoidCallback onTap,
+ required Widget child,
+ required double radius,
+ required String tooltip,
+ }) {
+ return Tooltip(
+ message: tooltip,
+ child: Material(
+ color: Colors.white,
+ shape: const CircleBorder(),
+ elevation: 4,
+ child: InkWell(
+ onTap: onTap,
+ customBorder: const CircleBorder(),
+ child: CircleAvatar(
+ radius: radius,
+ backgroundColor: Colors.white,
+ child: child,
),
),
- ],
+ ),
);
}
-// 底部编辑弹窗
+ // 底部编辑弹窗
void _bottomSheet(BuildContext context, bool isDarkMode) {
Get.bottomSheet(
- Padding(
- padding: MediaQuery.of(context).viewInsets,
- child: Container(
- padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w),
- decoration: BoxDecoration(
- color: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
- borderRadius: BorderRadius.vertical(top: Radius.circular(20.w)),
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- // 标题
- Padding(
- padding: EdgeInsets.only(bottom: 20.w),
- child: Text(
- 'editMeetingTitle'.tr,
- style: TextStyle(
- color: isDarkMode ? Colors.white : Colors.black87,
- fontSize: 18.sp,
- fontWeight: FontWeight.bold,
- ),
+ Container(
+ padding: EdgeInsets.only(
+ left: 20.w,
+ right: 20.w,
+ top: 20.w,
+ bottom: MediaQuery.of(context).viewInsets.bottom + 20.w,
+ ),
+ decoration: BoxDecoration(
+ color: isDarkMode ? AppColors.darkBackground : Colors.white,
+ borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withValues(alpha: 0.1),
+ blurRadius: 10,
+ offset: const Offset(0, -2),
+ ),
+ ],
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // 拖拽指示器
+ Center(
+ child: Container(
+ width: 40.w,
+ height: 4.h,
+ margin: EdgeInsets.only(bottom: 20.h),
+ decoration: BoxDecoration(
+ color: Colors.grey[400],
+ borderRadius: BorderRadius.circular(2.r),
),
),
+ ),
+
+ // 标题
+ Text(
+ 'editMeetingTitle'.tr,
+ style: TextStyle(
+ color: isDarkMode ? Colors.white : Colors.black87,
+ fontSize: 20.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
- // 输入框
- Container(
- decoration: BoxDecoration(
- color: isDarkMode ? Colors.white10 : Colors.white,
- borderRadius: BorderRadius.circular(12.w),
+ SizedBox(height: 16.h),
+
+ // 输入框
+ Container(
+ decoration: BoxDecoration(
+ color: isDarkMode ? Colors.white10 : Colors.grey[100],
+ borderRadius: BorderRadius.circular(12.r),
+ border: Border.all(
+ color: isDarkMode ? Colors.white24 : Colors.grey[300]!,
+ width: 1,
),
- child: TextField(
- controller: controller.titleEditingController,
- autofocus: true, // 自动弹出键盘
- maxLength: 50, // 添加字符限制
- style: TextStyle(
- color: isDarkMode ? Colors.white : Colors.black87,
- fontSize: 16.sp,
- ),
- decoration: InputDecoration(
- hintText: 'maxCharacters'.tr,
- hintStyle: TextStyle(
- color: isDarkMode ? Colors.white60 : Colors.black45,
- ),
- border: InputBorder.none,
- contentPadding: EdgeInsets.all(16.w),
- counterText: '', // 隐藏默认计数器
+ ),
+ child: TextField(
+ controller: controller.titleEditingController,
+ autofocus: true,
+ maxLength: 50,
+ style: TextStyle(
+ color: isDarkMode ? Colors.white : Colors.black87,
+ fontSize: 16.sp,
+ ),
+ decoration: InputDecoration(
+ hintText: 'enterFileName'.tr,
+ hintStyle: TextStyle(
+ color: isDarkMode ? Colors.white60 : Colors.grey[600],
),
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.all(16.w),
+ counterText: '',
),
+ textInputAction: TextInputAction.done,
+ onSubmitted: (_) async {
+ await controller.saveTitleChange();
+ await controller.saveRecording();
+ Get.back();
+ },
),
+ ),
- Padding(
- padding: EdgeInsets.only(top: 30.w),
- child: Row(
- children: [
- // 取消按钮
- Expanded(
- child: TextButton(
- onPressed: Get.back, //
- style: TextButton.styleFrom(
- foregroundColor:
- isDarkMode ? AppColors.primary : Colors.blue,
- padding: EdgeInsets.symmetric(vertical: 12.w),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12.w),
- side: BorderSide(
- color:
- isDarkMode ? AppColors.primary : Colors.blue,
- width: 2.w,
- ),
- ),
- ),
- child: Text('cancel'.tr),
+ SizedBox(height: 24.h),
+
+ // 按钮组
+ Row(
+ children: [
+ Expanded(
+ child: OutlinedButton(
+ onPressed: Get.back,
+ style: OutlinedButton.styleFrom(
+ foregroundColor:
+ isDarkMode ? Colors.white70 : Colors.grey[700],
+ side: BorderSide(
+ color: isDarkMode ? Colors.white30 : Colors.grey[400]!,
+ ),
+ padding: EdgeInsets.symmetric(vertical: 14.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12.r),
),
),
-
- SizedBox(width: 20.w),
-
- // 确认按钮
- Expanded(
- child: TextButton(
- onPressed: () async {
- await controller.saveTitleChange();
- await controller.saveRecording();
- Get.back();
- },
- style: TextButton.styleFrom(
- backgroundColor:
- isDarkMode ? AppColors.primary : Colors.blue,
- foregroundColor: Colors.white,
- padding: EdgeInsets.symmetric(vertical: 12.w),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12.w),
- ),
- ),
- child: Text('confirm'.tr),
+ child: Text('cancel'.tr),
+ ),
+ ),
+ SizedBox(width: 16.w),
+ Expanded(
+ child: ElevatedButton(
+ onPressed: () async {
+ await controller.saveTitleChange();
+ await controller.saveRecording();
+ Get.back();
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor:
+ isDarkMode ? AppColors.primary : Colors.blue,
+ foregroundColor: Colors.white,
+ padding: EdgeInsets.symmetric(vertical: 14.h),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12.r),
),
+ elevation: 2,
),
- ],
+ child: Text('confirm'.tr),
+ ),
),
- ),
- ],
- ),
+ ],
+ ),
+ ],
),
),
- isScrollControlled: true, // 防止键盘遮挡
- backgroundColor: Colors.transparent, // 移除默认背景
+ isScrollControlled: true,
+ backgroundColor: Colors.transparent,
+ isDismissible: true,
+ enableDrag: true,
);
}
}
-class WaveformPainter extends CustomPainter {
- final List data;
- final double cursorPosition;
+/// 实时流动波形绘制器
+class RealTimeWaveformPainter extends CustomPainter {
+ final List waveData;
+ final bool isDarkMode;
+ final bool isRecording;
+ final double animationValue; // 动画值,用于流动效果
- WaveformPainter(this.data, {required this.cursorPosition});
+ RealTimeWaveformPainter({
+ required this.waveData,
+ required this.isDarkMode,
+ required this.isRecording,
+ this.animationValue = 0.0,
+ });
@override
void paint(Canvas canvas, Size size) {
- final paint = Paint()
- ..color = Colors.blue[300]!
- ..strokeWidth = 2
- ..strokeCap = StrokeCap.round;
+ final centerY = size.height / 2;
+ final centerX = size.width / 2;
+
+ // 绘制中央基准线
+ _drawCenterLine(canvas, size, centerX, centerY);
+
+ // 只在录音状态下且有波形数据时才绘制波形
+ if (isRecording && waveData.isNotEmpty) {
+ _drawWaveform(canvas, size, centerX, centerY);
+ }
+ }
- final cursorPaint = Paint()
+ /// 绘制中央时间参考线
+ void _drawCenterLine(
+ Canvas canvas, Size size, double centerX, double centerY) {
+ // 绘制水平中线
+ final horizontalLinePaint = Paint()
+ ..color =
+ (isDarkMode ? Colors.white : Colors.black).withValues(alpha: 0.15)
+ ..strokeWidth = 1.0;
+
+ canvas.drawLine(
+ Offset(0, centerY),
+ Offset(size.width, centerY),
+ horizontalLinePaint,
+ );
+
+ // 绘制垂直时间参考线(红色,更醒目)
+ final centerLinePaint = Paint()
+ ..color = Colors.red.withValues(alpha: 0.8)
+ ..strokeWidth = 2.0
+ ..style = PaintingStyle.stroke;
+
+ canvas.drawLine(
+ Offset(centerX, centerY - size.height * 0.4),
+ Offset(centerX, centerY + size.height * 0.4),
+ centerLinePaint,
+ );
+
+ // 在参考线顶部绘制小三角形指示器
+ final trianglePath = Path();
+ final triangleSize = 4.0;
+ trianglePath.moveTo(centerX, centerY - size.height * 0.4);
+ trianglePath.lineTo(
+ centerX - triangleSize, centerY - size.height * 0.4 - triangleSize);
+ trianglePath.lineTo(
+ centerX + triangleSize, centerY - size.height * 0.4 - triangleSize);
+ trianglePath.close();
+
+ final trianglePaint = Paint()
..color = Colors.red
- ..strokeWidth = 2;
+ ..style = PaintingStyle.fill;
- final double barWidth = size.width / (data.length * 1.2); // 增加条宽
- final centerY = size.height / 2;
+ canvas.drawPath(trianglePath, trianglePaint);
+ }
- // 改为绘制矩形条,增加视觉冲击力
- for (int i = 0; i < data.length; i++) {
- final x = i * barWidth;
- final height = min(data[i], 50); // 限制最大高度
- final rect = Rect.fromLTWH(
- x,
- centerY - height / 2,
- barWidth * 0.8, // 条宽稍小于间隔
- height.toDouble(),
- );
+ /// 绘制实时流动波形
+ void _drawWaveform(Canvas canvas, Size size, double centerX, double centerY) {
+ final barWidth = 2.5; // 波形条宽度
+ final barSpacing = 0.5; // 波形条间距
+ final totalBarWidth = barWidth + barSpacing;
+
+ // 计算中央参考线左侧可以显示多少个波形条
+ final maxBarsOnLeft = (centerX / totalBarWidth).floor();
+
+ // 绘制左侧流动波形(历史数据,从右向左流动)
+ _drawLeftWaveform(
+ canvas, centerX, centerY, barWidth, totalBarWidth, maxBarsOnLeft);
+ }
+
+ /// 绘制左侧历史波形(从右向左流动)
+ void _drawLeftWaveform(Canvas canvas, double centerX, double centerY,
+ double barWidth, double totalBarWidth, int maxBars) {
+ final dataLength = waveData.length;
+ if (dataLength == 0) return;
+
+ // 计算流动偏移量
+ final flowOffset = animationValue * totalBarWidth;
+
+ // 绘制最近的波形条(最多显示maxBars个)
+ final displayCount = min(dataLength, maxBars);
+
+ for (int i = 0; i < displayCount; i++) {
+ // 计算波形条的x位置:从参考线开始,向左排列,加上流动偏移
+ final x = centerX - (i + 1) * totalBarWidth + flowOffset;
+
+ // 如果波形条完全移出屏幕左侧,跳过绘制
+ if (x + barWidth < 0) continue;
+
+ // 获取对应的音频数据(最新的数据在数组末尾)
+ final dataIndex = dataLength - 1 - i;
+ if (dataIndex < 0) continue;
- // 添加渐变效果增强视觉
- final gradient = LinearGradient(
- colors: [Colors.blue[400]!, Colors.blue[200]!],
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
+ final amplitude = waveData[dataIndex];
+
+ // 计算透明度:越远的波形条越透明
+ final opacity = (1.0 - (i / displayCount) * 0.6).clamp(0.2, 1.0);
+
+ _drawWaveBar(canvas, x, centerY, barWidth, amplitude, opacity);
+ }
+ }
+
+ /// 绘制单个上下对称的波形条
+ void _drawWaveBar(Canvas canvas, double x, double centerY, double barWidth,
+ double amplitude, double opacity) {
+ // 将振幅转换为显示高度(上下对称)
+ final maxHeight = 50.0; // 最大高度
+ final barHeight = (amplitude / 100.0) * maxHeight;
+
+ // 创建上下对称的圆角矩形
+ final rect = RRect.fromRectAndRadius(
+ Rect.fromCenter(
+ center: Offset(x + barWidth / 2, centerY),
+ width: barWidth,
+ height: barHeight * 2, // 上下对称,总高度是barHeight的2倍
+ ),
+ Radius.circular(barWidth / 2),
+ );
+
+ // 创建渐变效果
+ final gradient = LinearGradient(
+ colors: _getGradientColors(opacity),
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ );
+
+ final paint = Paint()
+ ..shader = gradient.createShader(rect.outerRect)
+ ..style = PaintingStyle.fill;
+
+ canvas.drawRRect(rect, paint);
+
+ // 可选:添加高光效果(仅对较新的波形条)
+ if (opacity > 0.7) {
+ final highlightRect = RRect.fromRectAndRadius(
+ Rect.fromCenter(
+ center: Offset(x + barWidth / 2, centerY),
+ width: barWidth * 0.4,
+ height: barHeight * 2 * 0.8,
+ ),
+ Radius.circular(barWidth / 4),
);
- final rectPaint = Paint()
- ..shader = gradient.createShader(rect)
+
+ final highlightPaint = Paint()
+ ..color = Colors.white.withValues(alpha: 0.2 * opacity)
..style = PaintingStyle.fill;
- canvas.drawRRect(
- RRect.fromRectAndRadius(rect, const Radius.circular(4)),
- rectPaint,
- );
+ canvas.drawRRect(highlightRect, highlightPaint);
}
+ }
- final cursorX = size.width * cursorPosition;
- canvas.drawLine(
- Offset(cursorX, 0),
- Offset(cursorX, size.height),
- cursorPaint,
- );
+ /// 获取渐变颜色
+ List _getGradientColors(double opacity) {
+ return isDarkMode
+ ? [
+ Colors.blue[400]!.withValues(alpha: opacity),
+ Colors.blue[200]!.withValues(alpha: opacity * 0.7),
+ Colors.blue[100]!.withValues(alpha: opacity * 0.4),
+ ]
+ : [
+ Colors.blue[500]!.withValues(alpha: opacity),
+ Colors.blue[300]!.withValues(alpha: opacity * 0.7),
+ Colors.blue[100]!.withValues(alpha: opacity * 0.4),
+ ];
}
@override
- bool shouldRepaint(covariant WaveformPainter oldDelegate) {
- return oldDelegate.data != data ||
- oldDelegate.cursorPosition != cursorPosition;
+ bool shouldRepaint(covariant RealTimeWaveformPainter oldDelegate) {
+ return oldDelegate.waveData != waveData ||
+ oldDelegate.isDarkMode != isDarkMode ||
+ oldDelegate.isRecording != isRecording ||
+ oldDelegate.animationValue != animationValue;
+ }
+}
+
+/// 专门的波形Widget,正确管理GetX响应式状态
+class _WaveformWidget extends StatelessWidget {
+ final MeetingRecordController controller;
+ final bool isDarkMode;
+ final Size size;
+
+ const _WaveformWidget({
+ required this.controller,
+ required this.isDarkMode,
+ required this.size,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ // 使用AnimatedBuilder监听动画变化
+ return AnimatedBuilder(
+ animation: controller.waveformAnimation,
+ builder: (context, child) {
+ // 使用Obx只监听需要响应式更新的数据
+ return Obx(() {
+ return CustomPaint(
+ painter: RealTimeWaveformPainter(
+ waveData: controller.waveData,
+ isDarkMode: isDarkMode,
+ isRecording: controller.isRecording.value,
+ animationValue: controller.waveformAnimation.value,
+ ),
+ size: size,
+ );
+ });
+ },
+ );
}
}
diff --git a/lib/modules/permissions/bindings/permissions_binding.dart b/lib/modules/permissions/bindings/permissions_binding.dart
new file mode 100644
index 000000000..2b0547984
--- /dev/null
+++ b/lib/modules/permissions/bindings/permissions_binding.dart
@@ -0,0 +1,9 @@
+import 'package:get/get.dart';
+import '../controllers/permissions_controller.dart';
+
+class PermissionsBinding extends Bindings {
+ @override
+ void dependencies() {
+ Get.lazyPut(() => PermissionsController());
+ }
+}
\ No newline at end of file
diff --git a/lib/modules/permissions/controllers/permissions_controller.dart b/lib/modules/permissions/controllers/permissions_controller.dart
new file mode 100644
index 000000000..4034cb772
--- /dev/null
+++ b/lib/modules/permissions/controllers/permissions_controller.dart
@@ -0,0 +1,316 @@
+import 'package:get/get.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:flutter/material.dart';
+import 'dart:io' show Platform;
+import 'package:device_info_plus/device_info_plus.dart';
+import '../../../core/utils/permission_util.dart';
+
+class PermissionsController extends GetxController {
+ // 权限状态映射
+ final RxMap permissionStatuses =
+ {}.obs;
+
+ // 是否正在请求权限
+ final RxBool isRequestingPermission = false.obs;
+
+ // Android版本信息(用于权限组显示判断)
+ final RxInt androidSdkVersion = 0.obs;
+
+ @override
+ void onInit() {
+ super.onInit();
+ _initAndroidVersion();
+ checkAllPermissions();
+ }
+
+ /// 初始化Android版本信息
+ Future _initAndroidVersion() async {
+ if (Platform.isAndroid) {
+ try {
+ final deviceInfo = DeviceInfoPlugin();
+ final androidInfo = await deviceInfo.androidInfo;
+ androidSdkVersion.value = androidInfo.version.sdkInt;
+ } catch (e) {
+ print('获取Android版本信息失败: $e');
+ androidSdkVersion.value = 0;
+ }
+ }
+ }
+
+ /// 检查是否应该显示蓝牙权限组
+ bool shouldShowBluetoothPermissions() {
+ if (Platform.isAndroid) {
+ // Android 12+ (API 31+) 才显示蓝牙权限组
+ return androidSdkVersion.value >= 31;
+ } else if (Platform.isIOS) {
+ // iOS显示蓝牙权限组
+ return true;
+ }
+ return false;
+ }
+
+ /// 检查是否应该显示存储权限组
+ bool shouldShowStoragePermissions() {
+ if (Platform.isAndroid) {
+ // Android 10及以下版本 (API 29及以下) 才显示存储权限组
+ return androidSdkVersion.value <= 29 && androidSdkVersion.value > 0;
+ }
+ // iOS系统不显示存储权限组
+ return false;
+ }
+
+ /// 检查所有权限状态
+ Future