Skip to content

Commit 7cbf1a6

Browse files
committed
♻️ Split viewer.
1 parent 1731cc3 commit 7cbf1a6

File tree

2 files changed

+237
-153
lines changed

2 files changed

+237
-153
lines changed

lib/src/widget/camera_picker.dart

Lines changed: 76 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
///
55
import 'dart:async';
66
import 'dart:io';
7-
import 'dart:typed_data';
87

98
import 'package:flutter/material.dart';
109
import 'package:flutter/services.dart';
@@ -15,6 +14,7 @@ import '../constants/constants.dart';
1514
import '../widget/circular_progress_bar.dart';
1615

1716
import 'builder/slide_page_transition_builder.dart';
17+
import 'camera_picker_viewer.dart';
1818

1919
/// Create a camera picker integrate with [CameraDescription].
2020
/// 通过 [CameraDescription] 整合的拍照选择
@@ -331,7 +331,75 @@ class CameraPickerState extends State<CameraPicker> {
331331
}
332332
}
333333

334-
/// Make sure the [takenFilePath] is `null` before pop.
334+
/// When the [shootingButton]'s `onLongPress` called, the timer [recordDetectTimer]
335+
/// will be initialized to achieve press time detection. If the duration
336+
/// reached to same as [recordDetectDuration], and the timer still active,
337+
/// start recording video.
338+
///
339+
/// 当 [shootingButton] 触发了长按,初始化一个定时器来实现时间检测。如果长按时间
340+
/// 达到了 [recordDetectDuration] 且定时器未被销毁,则开始录制视频。
341+
void recordDetection() {
342+
recordDetectTimer = Timer(recordDetectDuration, () {
343+
startRecordingVideo();
344+
if (mounted) {
345+
setState(() {});
346+
}
347+
});
348+
setState(() {
349+
isShootingButtonAnimate = true;
350+
});
351+
}
352+
353+
/// This will be given to the [Listener] in the [shootingButton]. When it's
354+
/// called, which means no more pressing on the button, cancel the timer and
355+
/// reset the status.
356+
///
357+
/// 这个方法会赋值给 [shootingButton] 中的 [Listener]。当按钮释放了点击后,定时器
358+
/// 将被取消,并且状态会重置。
359+
void recordDetectionCancel(PointerUpEvent event) {
360+
recordDetectTimer?.cancel();
361+
if (isRecording) {
362+
stopRecordingVideo();
363+
if (mounted) {
364+
setState(() {});
365+
}
366+
}
367+
if (isShootingButtonAnimate) {
368+
isShootingButtonAnimate = false;
369+
if (mounted) {
370+
setState(() {});
371+
}
372+
}
373+
}
374+
375+
/// Set record file path and start recording.
376+
/// 设置拍摄文件路径并开始录制视频
377+
void startRecordingVideo() {
378+
final String filePath = '${cacheFilePath}_$currentTimeStamp.mp4';
379+
if (!cameraController.value.isRecordingVideo) {
380+
cameraController.startVideoRecording(filePath).catchError((dynamic e) {
381+
realDebugPrint('Error when recording video: $e');
382+
if (cameraController.value.isRecordingVideo) {
383+
cameraController.stopVideoRecording().catchError((dynamic e) {
384+
realDebugPrint('Error when stop recording video: $e');
385+
});
386+
}
387+
});
388+
}
389+
}
390+
391+
/// Stop
392+
Future<void> stopRecordingVideo() async {
393+
if (cameraController.value.isRecordingVideo) {
394+
cameraController.stopVideoRecording().then((dynamic result) {
395+
// TODO(Alex): Jump to the viewer.
396+
}).catchError((dynamic e) {
397+
realDebugPrint('Error when stop recording video: $e');
398+
});
399+
}
400+
}
401+
402+
/// Make sure the [takenPictureFilePath] is `null` before pop.
335403
/// Otherwise, make it `null` .
336404
Future<bool> clearTakenFileBeforePop() async {
337405
if (takenPictureFilePath != null) {
@@ -343,27 +411,6 @@ class CameraPickerState extends State<CameraPicker> {
343411
return true;
344412
}
345413

346-
/// When users confirm to use the taken file, create the [AssetEntity] using
347-
/// [Editor.saveImage] (PhotoManager.editor.saveImage), then delete the file
348-
/// if not [shouldKeptInLocal]. While the entity might returned null, there's
349-
/// no side effects if popping `null` because the parent picker will ignore it.
350-
Future<void> createAssetEntityAndPop() async {
351-
try {
352-
final File file = takenPictureFile;
353-
final Uint8List data = await file.readAsBytes();
354-
final AssetEntity entity = await PhotoManager.editor.saveImage(
355-
data,
356-
title: takenPictureFilePath,
357-
);
358-
if (!shouldKeptInLocal) {
359-
file.delete();
360-
}
361-
Navigator.of(context).pop(entity);
362-
} catch (e) {
363-
realDebugPrint('Error when creating entity: $e');
364-
}
365-
}
366-
367414
/// Settings action section widget.
368415
/// 设置操作区
369416
///
@@ -467,54 +514,23 @@ class CameraPickerState extends State<CameraPicker> {
467514
final Size outerSize = Size.square(Screens.width / 3.5);
468515
return Listener(
469516
behavior: HitTestBehavior.opaque,
470-
onPointerUp: isAllowRecording
471-
? (PointerUpEvent event) {
472-
recordDetectTimer?.cancel();
473-
if (isRecording) {
474-
isRecording = false;
475-
if (mounted) {
476-
setState(() {});
477-
}
478-
}
479-
if (isShootingButtonAnimate) {
480-
isShootingButtonAnimate = false;
481-
if (mounted) {
482-
setState(() {});
483-
}
484-
}
485-
}
486-
: null,
517+
onPointerUp: isAllowRecording ? recordDetectionCancel : null,
487518
child: InkWell(
488519
borderRadius: maxBorderRadius,
489520
onTap: takePicture,
490-
onLongPress: isAllowRecording
491-
? () {
492-
recordDetectTimer = Timer(recordDetectDuration, () {
493-
isRecording = true;
494-
if (mounted) {
495-
setState(() {});
496-
}
497-
});
498-
setState(() {
499-
isShootingButtonAnimate = true;
500-
});
501-
}
502-
: null,
521+
onLongPress: isAllowRecording ? recordDetection : null,
503522
child: SizedBox.fromSize(
504523
size: outerSize,
505524
child: Stack(
506525
children: <Widget>[
507526
Center(
508527
child: AnimatedContainer(
509528
duration: kThemeChangeDuration,
510-
width: isShootingButtonAnimate
511-
? outerSize.width
512-
: (Screens.width / 5),
513-
height: isShootingButtonAnimate
514-
? outerSize.height
515-
: (Screens.width / 5),
529+
width: isShootingButtonAnimate ? outerSize.width : (Screens.width / 5),
530+
height: isShootingButtonAnimate ? outerSize.height : (Screens.width / 5),
516531
padding: EdgeInsets.all(
517-
Screens.width / (isShootingButtonAnimate ? 10 : 35)),
532+
Screens.width / (isShootingButtonAnimate ? 10 : 35),
533+
),
518534
decoration: BoxDecoration(
519535
color: Colors.white30,
520536
shape: BoxShape.circle,
@@ -540,98 +556,6 @@ class CameraPickerState extends State<CameraPicker> {
540556
);
541557
}
542558

543-
/// The preview section for the taken file.
544-
/// 拍摄文件的预览区
545-
Widget get takenFilePreviewWidget {
546-
return ColoredBox(
547-
color: Colors.black,
548-
child: Stack(
549-
children: <Widget>[
550-
Positioned.fill(child: Image.file(takenPictureFile)),
551-
SafeArea(
552-
child: Padding(
553-
padding: const EdgeInsets.symmetric(
554-
horizontal: 8.0,
555-
vertical: 20.0,
556-
),
557-
child: Column(
558-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
559-
children: <Widget>[
560-
Row(
561-
children: <Widget>[
562-
previewBackButton,
563-
const Spacer(),
564-
],
565-
),
566-
Row(
567-
children: <Widget>[
568-
const Spacer(),
569-
previewConfirmButton,
570-
],
571-
),
572-
],
573-
),
574-
),
575-
),
576-
],
577-
),
578-
);
579-
}
580-
581-
/// The back button for the preview section.
582-
/// 预览区的返回按钮
583-
Widget get previewBackButton {
584-
return InkWell(
585-
borderRadius: maxBorderRadius,
586-
onTap: () {
587-
takenPictureFile.delete();
588-
setState(() {
589-
takenPictureFilePath = null;
590-
});
591-
},
592-
child: Container(
593-
margin: const EdgeInsets.all(10.0),
594-
width: Screens.width / 15,
595-
height: Screens.width / 15,
596-
decoration: const BoxDecoration(
597-
color: Colors.white,
598-
shape: BoxShape.circle,
599-
),
600-
child: Center(
601-
child: Icon(
602-
Icons.close,
603-
color: Colors.black,
604-
size: 18.0,
605-
),
606-
),
607-
),
608-
);
609-
}
610-
611-
/// The confirm button for the preview section.
612-
/// 预览区的确认按钮
613-
Widget get previewConfirmButton {
614-
return MaterialButton(
615-
minWidth: 20.0,
616-
height: 32.0,
617-
padding: const EdgeInsets.symmetric(horizontal: 12.0),
618-
color: theme.colorScheme.secondary,
619-
shape: RoundedRectangleBorder(
620-
borderRadius: BorderRadius.circular(3.0),
621-
),
622-
child: Text(
623-
'完成',
624-
style: TextStyle(
625-
color: theme.textTheme.bodyText1.color,
626-
fontSize: 17.0,
627-
fontWeight: FontWeight.normal,
628-
),
629-
),
630-
onPressed: createAssetEntityAndPop,
631-
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
632-
);
633-
}
634-
635559
@override
636560
Widget build(BuildContext context) {
637561
return WillPopScope(
@@ -664,7 +588,6 @@ class CameraPickerState extends State<CameraPicker> {
664588
),
665589
),
666590
),
667-
if (takenPictureFilePath != null) takenFilePreviewWidget,
668591
],
669592
),
670593
),

0 commit comments

Comments
 (0)