1.广角/主摄切换按钮已上移:

2.新增 isSwitchingLens 状态、避免切换镜头    stop 按钮禁用,不会触发 stopRecording
倍距按钮禁用,避免连续切换
ViewModel 层也兜底:isSwitchingLens=true 时直接拒绝 stop
3.合并失败兜底提示
This commit is contained in:
2026-07-01 10:59:28 +08:00
parent 02614c2817
commit c8a7494344
6 changed files with 205 additions and 11 deletions
@@ -7,6 +7,7 @@ class RecordingSessionState {
this.isTouchLocked = true,
this.isPreviewReady = false,
this.isStartingRecording = false,
this.isSwitchingLens = false,
this.hasDndAccess = false,
this.isBatteryOptimizedIgnored = true,
this.notificationsGranted = true,
@@ -19,12 +20,14 @@ class RecordingSessionState {
this.errorMessage,
this.permissionWarning,
this.fileSaveFailed = false,
this.segmentOutputPaths = const [],
});
final RecordingStatus status;
final bool isTouchLocked;
final bool isPreviewReady;
final bool isStartingRecording;
final bool isSwitchingLens;
final bool hasDndAccess;
final bool isBatteryOptimizedIgnored;
final bool notificationsGranted;
@@ -37,6 +40,7 @@ class RecordingSessionState {
final String? errorMessage;
final String? permissionWarning;
final bool fileSaveFailed;
final List<String> segmentOutputPaths;
bool get isRecording => status.isRecording;
@@ -53,6 +57,7 @@ class RecordingSessionState {
bool? isTouchLocked,
bool? isPreviewReady,
bool? isStartingRecording,
bool? isSwitchingLens,
bool? hasDndAccess,
bool? isBatteryOptimizedIgnored,
bool? notificationsGranted,
@@ -65,6 +70,7 @@ class RecordingSessionState {
String? errorMessage,
String? permissionWarning,
bool? fileSaveFailed,
List<String>? segmentOutputPaths,
bool clearPermissionWarning = false,
bool clearLastSaved = false,
}) {
@@ -73,6 +79,7 @@ class RecordingSessionState {
isTouchLocked: isTouchLocked ?? this.isTouchLocked,
isPreviewReady: isPreviewReady ?? this.isPreviewReady,
isStartingRecording: isStartingRecording ?? this.isStartingRecording,
isSwitchingLens: isSwitchingLens ?? this.isSwitchingLens,
hasDndAccess: hasDndAccess ?? this.hasDndAccess,
isBatteryOptimizedIgnored:
isBatteryOptimizedIgnored ?? this.isBatteryOptimizedIgnored,
@@ -90,6 +97,7 @@ class RecordingSessionState {
? null
: (permissionWarning ?? this.permissionWarning),
fileSaveFailed: fileSaveFailed ?? this.fileSaveFailed,
segmentOutputPaths: segmentOutputPaths ?? this.segmentOutputPaths,
);
}
}
@@ -173,7 +173,11 @@ class _RecordingPageState extends ConsumerState<RecordingPage> {
if (!mounted) return;
final latest = ref.read(recordingViewModelProvider).session;
if (latest.fileSaveFailed) {
AppToast.show(latest.errorMessage ?? '保存到文件夹失败,请检查文件保存权限');
if (latest.segmentOutputPaths.isNotEmpty) {
AppToast.show('视频合并失败,已为你保存分段文件,可在相册中查看');
} else {
AppToast.show(latest.errorMessage ?? '保存到文件夹失败,请检查文件保存权限');
}
return;
}
await _showRecordingSavedDialogIfNeeded();
@@ -374,6 +378,7 @@ class _RecordingHudLayer extends ConsumerWidget {
m.session.notificationsGranted,
m.session.isRecording,
m.session.isStartingRecording,
m.session.isSwitchingLens,
m.session.isTouchLocked,
m.session.zoomRatio,
m.session.minZoomRatio,
@@ -391,6 +396,7 @@ class _RecordingHudLayer extends ConsumerWidget {
notificationsGranted,
isRecording,
isStartingRecording,
isSwitchingLens,
isTouchLocked,
zoomRatio,
minZoomRatio,
@@ -408,6 +414,7 @@ class _RecordingHudLayer extends ConsumerWidget {
notificationsGranted: notificationsGranted,
isRecording: isRecording,
isStartingRecording: isStartingRecording,
isSwitchingLens: isSwitchingLens,
isTouchLocked: isTouchLocked,
zoomRatio: zoomRatio,
minZoomRatio: minZoomRatio,
@@ -349,10 +349,14 @@ class RecordingViewModel extends Notifier<RecordingModel> {
/// 设置相机倍距,原生层会返回设备实际应用后的倍距范围与当前值。
Future<void> setZoomRatio(double ratio) async {
final session = state.session;
if (session.isSwitchingLens) {
return;
}
final clamped = ratio
.clamp(session.minZoomRatio, session.maxZoomRatio)
.toDouble();
_updateSession((s) => s.copyWith(isSwitchingLens: true));
try {
final zoom = await RecordingPlatform.setZoomRatio(clamped);
_updateSession(
@@ -368,13 +372,19 @@ class RecordingViewModel extends Notifier<RecordingModel> {
? '切换镜头失败,请重试'
: (error.message ?? '相机倍距设置失败');
_updateSession((s) => s.copyWith(errorMessage: message));
} finally {
_updateSession(
(s) => s.copyWith(isSwitchingLens: false, errorMessage: s.errorMessage),
);
}
}
/// 开始录制,可选开启勿扰模式。
Future<void> startRecording({bool enableDoNotDisturb = true}) async {
final session = state.session;
if (session.isRecording || session.isStartingRecording) {
if (session.isRecording ||
session.isStartingRecording ||
session.isSwitchingLens) {
return;
}
if (!session.isPreviewReady) {
@@ -401,6 +411,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
isTouchLocked: true,
errorMessage: null,
fileSaveFailed: false,
segmentOutputPaths: const [],
clearLastSaved: true,
),
);
@@ -415,7 +426,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
/// 停止录制、保存到文件夹,并恢复相机预览。
Future<void> stopRecording() async {
if (!state.session.isRecording) return;
if (!state.session.isRecording || state.session.isSwitchingLens) return;
try {
final result = await RecordingPlatform.stopRecording();
@@ -432,6 +443,7 @@ class RecordingViewModel extends Notifier<RecordingModel> {
? (result.fileErrorMessage ?? '保存到文件夹失败,请检查文件保存权限')
: null,
fileSaveFailed: fileFailed,
segmentOutputPaths: result.segmentOutputPaths,
),
);
} on PlatformException catch (error) {
@@ -18,6 +18,7 @@ class RecordingHudWidget extends StatelessWidget {
required this.notificationsGranted,
required this.isRecording,
required this.isStartingRecording,
required this.isSwitchingLens,
required this.isTouchLocked,
this.showClipboardHint = false,
this.clipboardAddress = '',
@@ -39,6 +40,7 @@ class RecordingHudWidget extends StatelessWidget {
final bool notificationsGranted;
final bool isRecording;
final bool isStartingRecording;
final bool isSwitchingLens;
final bool isTouchLocked;
final bool showClipboardHint;
final String clipboardAddress;
@@ -143,8 +145,9 @@ class RecordingHudWidget extends StatelessWidget {
),
Positioned(
right: 16.r,
bottom: _recordButtonBottom + _recordButtonSize + 14.h,
bottom: 260.r,
child: _ZoomPresetControl(
enabled: !isSwitchingLens,
zoomRatio: zoomRatio,
minZoomRatio: minZoomRatio,
maxZoomRatio: maxZoomRatio,
@@ -160,7 +163,7 @@ class RecordingHudWidget extends StatelessWidget {
child: RecordingControlButton(
isRecording: isRecording,
isStartingRecording: isStartingRecording,
enabled: !isStartingRecording,
enabled: !isStartingRecording && !isSwitchingLens,
size: _recordButtonSize,
onTap: () {
if (isRecording) {
@@ -197,6 +200,7 @@ class RecordingHudWidget extends StatelessWidget {
class _ZoomPresetControl extends StatelessWidget {
const _ZoomPresetControl({
required this.enabled,
required this.zoomRatio,
required this.minZoomRatio,
required this.maxZoomRatio,
@@ -204,6 +208,7 @@ class _ZoomPresetControl extends StatelessWidget {
required this.onSelected,
});
final bool enabled;
final double zoomRatio;
final double minZoomRatio;
final double maxZoomRatio;
@@ -235,7 +240,7 @@ class _ZoomPresetControl extends StatelessWidget {
displayRatio: preset,
requestRatio: preset,
selected: _isPresetSelected(preset),
enabled: true,
enabled: enabled,
onSelected: onSelected,
),
],