@@ -13,6 +13,7 @@ import 'package:recording_tool/features/recording/model/model_recording_session.
import ' package:recording_tool/features/recording/platform/recording_platform.dart ' ;
import ' package:recording_tool/features/recording/utils/recording_display_name.dart ' ;
/// 录制页状态 Provider。
final recordingViewModelProvider =
NotifierProvider < RecordingViewModel , RecordingModel > (
RecordingViewModel . new ,
@@ -30,6 +31,20 @@ 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 < RecordingModel > {
static final _defaultClipboard = ClipboardRecordingModel (
title: ' ' ,
@@ -38,19 +53,21 @@ class RecordingViewModel extends Notifier<RecordingModel> {
StreamSubscription < RecordingStatus > ? _statusSubscription ;
/// 初始化状态并注册销毁回调。
@ override
RecordingModel build ( ) {
ref . onDispose ( _dispose ) ;
return RecordingModel ( clipboardRecordingModel: _defaultClipboard ) ;
}
/// 局部更新 session 子状态。
void _updateSession (
RecordingSessionState Function ( RecordingSessionState session ) update ,
) {
state = state . copyWith ( session: update ( state . session ) ) ;
}
/// 从剪切板获取小程序复制的 录制信息。
/// 读取并解析剪贴板中的小程序 录制信息。
Future < ClipboardReadResult > getClipboardContent ( ) async {
try {
final clipboardData = await Clipboard . getData ( Clipboard . kTextPlain ) ;
@@ -94,10 +111,12 @@ class RecordingViewModel extends Notifier<RecordingModel> {
}
}
/// 清空剪贴板赛事信息(供 UI 调用)。
void resetClipboardInfo ( ) {
_resetClipboardInfo ( ) ;
}
/// 重置剪贴板赛事信息为默认空值。
void _resetClipboardInfo ( ) {
state = state . copyWith (
clipboardRecordingModel: _defaultClipboard ,
@@ -105,6 +124,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
) ;
}
/// 申请权限、检查系统设置并初始化相机预览。
Future < void > prepareSession ( ) async {
if ( ! RecordingPlatform . isSupported ) {
_updateSession ( ( s ) = > s . copyWith ( errorMessage: ' 当前设备不支持录制 ' ) ) ;
@@ -179,6 +199,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
}
}
/// 初始化相机预览, PlatformView 未就绪时自动重试。
Future < RecordingStatus > _initializePreviewWithRetry ( ) async {
const maxAttempts = 8 ;
for ( var attempt = 0 ; attempt < maxAttempts ; attempt + + ) {
@@ -196,7 +217,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
throw StateError ( ' initializePreview retry exhausted ' ) ;
}
/// 停止录制后重新绑定相机预览(重置 isPreviewReady 以 显示加载遮罩) 。
/// 停止录制后重新绑定相机预览,并 显示加载遮罩。
Future < void > restorePreview ( ) async {
if ( ! RecordingPlatform . isSupported ) return ;
@@ -222,6 +243,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
}
}
/// 当前平台所需的相册/视频保存权限列表。
List < Permission > _galleryPermissions ( ) {
if ( Platform . isIOS ) {
return [ Permission . photosAddOnly , Permission . photos ] ;
@@ -232,6 +254,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
return const [ ] ;
}
/// 判断相册相关权限是否至少有一项已授予。
bool _isGalleryPermissionGranted (
Map < Permission , PermissionStatus > permissions ,
) {
@@ -243,11 +266,46 @@ class RecordingViewModel extends Notifier<RecordingModel> {
return _galleryPermissions ( ) . isEmpty ;
}
/// 检测并尝试申请相机、麦克风权限,同步更新 session 中的 isMicrophoneGranted。
Future < RecordingRequiredPermissions > 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 ) ) ;
if ( cameraGranted & & ! state . session . isPreviewReady ) {
_updateSession ( ( s ) = > s . copyWith ( errorMessage: null ) ) ;
await _listenStatus ( ) ;
await restorePreview ( ) ;
}
return RecordingRequiredPermissions (
cameraGranted: cameraGranted ,
microphoneGranted: microphoneGranted ,
) ;
}
bool _isPermissionGranted ( PermissionStatus ? status ) {
return status ? . isGranted = = true | | status ? . isLimited = = true ;
}
/// 开始录制,可选开启勿扰模式。
Future < void > startRecording ( { bool enableDoNotDisturb = true } ) async {
final session = state . session ;
if ( ! session . isPreviewReady | |
session . isRecording | |
session . isStartingRecording ) {
if ( session . isRecording | | session . isStartingRecording ) {
return ;
}
if ( ! session . isPreviewReady ) {
_updateSession (
( s ) = > s . copyWith ( errorMessage: ' 相机预览未就绪,请稍后重试 ' ) ,
) ;
return ;
}
@@ -282,6 +340,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
}
}
/// 停止录制、保存到相册,并恢复相机预览。
Future < void > stopRecording ( ) async {
if ( ! state . session . isRecording ) return ;
@@ -311,30 +370,37 @@ class RecordingViewModel extends Notifier<RecordingModel> {
}
}
/// 切换录制中触屏锁定状态。
void setTouchLocked ( bool locked ) {
_updateSession ( ( s ) = > s . copyWith ( isTouchLocked: locked ) ) ;
}
/// 清除上次保存成功的录制结果标记。
void clearSavedRecordingResult ( ) {
_updateSession ( ( s ) = > s . copyWith ( clearLastSaved: true ) ) ;
}
/// 跳转系统勿扰/通知策略设置页。
Future < void > openDndSettings ( ) = >
RecordingPlatform . openNotificationPolicySettings ( ) ;
/// 重新检测勿扰模式权限并更新状态。
Future < void > refreshDndAccess ( ) async {
final hasDnd = await RecordingPlatform . hasNotificationPolicyAccess ( ) ;
_updateSession ( ( s ) = > s . copyWith ( hasDndAccess: hasDnd ) ) ;
}
/// 跳转电池优化白名单设置页。
Future < void > openBatterySettings ( ) = >
RecordingPlatform . openBatteryOptimizationSettings ( ) ;
/// 重新检测是否已忽略电池优化并更新状态。
Future < void > refreshBatteryOptimization ( ) async {
final ignored = await RecordingPlatform . isIgnoringBatteryOptimizations ( ) ;
_updateSession ( ( s ) = > s . copyWith ( isBatteryOptimizedIgnored: ignored ) ) ;
}
/// 退出录制页时释放相机、勿扰和状态订阅。
Future < void > teardown ( ) async {
await RecordingPlatform . setImmersiveMode ( enabled: false ) ;
await RecordingPlatform . disableDoNotDisturb ( ) ;
@@ -344,6 +410,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
state = state . copyWith ( session: const RecordingSessionState ( ) ) ;
}
/// 订阅原生层录制状态流并同步到 session。
Future < void > _listenStatus ( ) async {
await _statusSubscription ? . cancel ( ) ;
_statusSubscription = RecordingPlatform . statusStream ( ) . listen ( ( status ) {
@@ -351,6 +418,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
} ) ;
}
/// Provider 销毁时取消状态流订阅。
Future < void > _dispose ( ) async {
await _statusSubscription ? . cancel ( ) ;
}