diff --git a/lib/features/recording/pages/page_record.dart b/lib/features/recording/pages/page_record.dart index c5e4916..c438ffa 100644 --- a/lib/features/recording/pages/page_record.dart +++ b/lib/features/recording/pages/page_record.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:recording_tool/core/platform/app_platform_info.dart'; import 'package:recording_tool/core/platform/device_health_checker.dart'; import 'package:recording_tool/features/dialog/dialog-record.dart'; @@ -118,13 +119,43 @@ class _RecordingPageState extends ConsumerState { ); } - /// 点击开始录制:校验剪贴板与健康状态 + /// 根据缺失权限生成弹窗文案。 + String _recordingPermissionDialogTitle(RecordingRequiredPermissions result) { + if (!result.cameraGranted && !result.microphoneGranted) { + return '录制需要开启相机和录音权限,请在系统设置中授权后重试'; + } + if (!result.cameraGranted) { + return '录制需要开启相机权限,请在系统设置中授权后重试'; + } + return '录制需要开启录音权限,请在系统设置中授权后重试'; + } + + /// 开始录制前检测相机、录音权限,未授予则弹窗并跳转系统设置。 + Future _ensureRecordingPermissions() async { + final result = await ref + .read(recordingViewModelProvider.notifier) + .ensureCameraAndMicrophonePermissions(); + if (result.allGranted) return true; + if (!mounted) return false; + + await RecordDialog.showSingle( + context, + title: _recordingPermissionDialogTitle(result), + buttonText: '确定', + onPressed: openAppSettings, + ); + return false; + } + + /// 点击开始录制:校验剪贴板、权限与健康状态 Future _onStartRecording() async { final recordingInfo = ref.read(recordingViewModelProvider); if (!recordingInfo.hasClipboardFilename) { await _showNoPlayerInfoDialog(); return; } + if (!await _ensureRecordingPermissions()) return; + if (!mounted) return; await _checkAndShowDeviceHealthAlerts(); if (!mounted) return; await ref.read(recordingViewModelProvider.notifier).startRecording(); diff --git a/lib/features/recording/view-model/view_model_recording.dart b/lib/features/recording/view-model/view_model_recording.dart index d42faf0..01429c3 100644 --- a/lib/features/recording/view-model/view_model_recording.dart +++ b/lib/features/recording/view-model/view_model_recording.dart @@ -31,6 +31,19 @@ enum ClipboardReadResult { invalid, } +/// 开始录制所需的相机/麦克风权限检测结果。 +class RecordingRequiredPermissions { + const RecordingRequiredPermissions({ + required this.cameraGranted, + required this.microphoneGranted, + }); + + final bool cameraGranted; + final bool microphoneGranted; + + bool get allGranted => cameraGranted && microphoneGranted; +} + /// 录制页 ViewModel:剪贴板、权限、相机预览与录制流程。 class RecordingViewModel extends Notifier { static final _defaultClipboard = ClipboardRecordingModel( @@ -253,6 +266,30 @@ class RecordingViewModel extends Notifier { return _galleryPermissions().isEmpty; } + /// 检测并尝试申请相机、麦克风权限,同步更新 session 中的 isMicrophoneGranted。 + Future ensureCameraAndMicrophonePermissions() async { + final permissions = await PermissionService.requestMissing([ + Permission.camera, + Permission.microphone, + ]); + + final cameraGranted = _isPermissionGranted(permissions[Permission.camera]); + final microphoneGranted = _isPermissionGranted( + permissions[Permission.microphone], + ); + + _updateSession((s) => s.copyWith(isMicrophoneGranted: microphoneGranted)); + + return RecordingRequiredPermissions( + cameraGranted: cameraGranted, + microphoneGranted: microphoneGranted, + ); + } + + bool _isPermissionGranted(PermissionStatus? status) { + return status?.isGranted == true || status?.isLimited == true; + } + /// 开始录制,可选开启勿扰模式。 Future startRecording({bool enableDoNotDisturb = true}) async { final session = state.session;