4
4
///
5
5
import 'dart:async' ;
6
6
import 'dart:io' ;
7
- import 'dart:typed_data' ;
8
7
9
8
import 'package:flutter/material.dart' ;
10
9
import 'package:flutter/services.dart' ;
@@ -15,6 +14,7 @@ import '../constants/constants.dart';
15
14
import '../widget/circular_progress_bar.dart' ;
16
15
17
16
import 'builder/slide_page_transition_builder.dart' ;
17
+ import 'camera_picker_viewer.dart' ;
18
18
19
19
/// Create a camera picker integrate with [CameraDescription] .
20
20
/// 通过 [CameraDescription] 整合的拍照选择
@@ -331,7 +331,75 @@ class CameraPickerState extends State<CameraPicker> {
331
331
}
332
332
}
333
333
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.
335
403
/// Otherwise, make it `null` .
336
404
Future <bool > clearTakenFileBeforePop () async {
337
405
if (takenPictureFilePath != null ) {
@@ -343,27 +411,6 @@ class CameraPickerState extends State<CameraPicker> {
343
411
return true ;
344
412
}
345
413
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
-
367
414
/// Settings action section widget.
368
415
/// 设置操作区
369
416
///
@@ -467,54 +514,23 @@ class CameraPickerState extends State<CameraPicker> {
467
514
final Size outerSize = Size .square (Screens .width / 3.5 );
468
515
return Listener (
469
516
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 ,
487
518
child: InkWell (
488
519
borderRadius: maxBorderRadius,
489
520
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 ,
503
522
child: SizedBox .fromSize (
504
523
size: outerSize,
505
524
child: Stack (
506
525
children: < Widget > [
507
526
Center (
508
527
child: AnimatedContainer (
509
528
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 ),
516
531
padding: EdgeInsets .all (
517
- Screens .width / (isShootingButtonAnimate ? 10 : 35 )),
532
+ Screens .width / (isShootingButtonAnimate ? 10 : 35 ),
533
+ ),
518
534
decoration: BoxDecoration (
519
535
color: Colors .white30,
520
536
shape: BoxShape .circle,
@@ -540,98 +556,6 @@ class CameraPickerState extends State<CameraPicker> {
540
556
);
541
557
}
542
558
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
-
635
559
@override
636
560
Widget build (BuildContext context) {
637
561
return WillPopScope (
@@ -664,7 +588,6 @@ class CameraPickerState extends State<CameraPicker> {
664
588
),
665
589
),
666
590
),
667
- if (takenPictureFilePath != null ) takenFilePreviewWidget,
668
591
],
669
592
),
670
593
),
0 commit comments