diff --git a/lib/extensions/snackbar.dart b/lib/extensions/snackbar.dart index 524ba99..e4529e1 100644 --- a/lib/extensions/snackbar.dart +++ b/lib/extensions/snackbar.dart @@ -35,6 +35,7 @@ extension ShowSnackBar on BuildContext { void showSuccessSnackBar({required final String message}) { showSnackBar( message: message, + duration: const Duration(milliseconds: 550), backgroundColor: Colors.green, ); } diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 5e3dc2b..a9759a2 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -28,6 +28,7 @@ class MainScreen extends StatefulWidget { class _MainScreenState extends AuthRequiredState with Loadable { bool isRecording = false; bool hasGrantedPermissions = false; + bool lockCamera = false; List? lastPhoto; Uint8List? uploadingPhotoAnimation; @@ -117,25 +118,37 @@ class _MainScreenState extends AuthRequiredState with Loadable { return; } - context.showPendingSnackBar(message: 'Taking photo...'); - - controller!.setFlashMode(FlashMode.off); - final file = File((await controller!.takePicture()).path); - setState(() { - uploadingPhotoAnimation = file.readAsBytesSync(); + lockCamera = true; }); - context.showPendingSnackBar(message: 'Uploading photo...'); - try { - await FileManager.uploadFile(_user, file); - } catch (error) { - context.showErrorSnackBar(message: error.toString()); - return; - } + context.showPendingSnackBar( + message: 'Taking photo, please hold still...', + ); - context.showSuccessSnackBar(message: 'Photo uploaded!'); + controller!.setFlashMode(FlashMode.off); + final file = File((await controller!.takePicture()).path); + + setState(() { + uploadingPhotoAnimation = file.readAsBytesSync(); + }); + + context.showPendingSnackBar(message: 'Uploading photo...'); + + try { + await FileManager.uploadFile(_user, file); + } catch (error) { + context.showErrorSnackBar(message: error.toString()); + return; + } + + context.showSuccessSnackBar(message: 'Photo uploaded!'); + } finally { + setState(() { + lockCamera = false; + }); + } if (mounted) { await getLastPhoto(); @@ -143,31 +156,41 @@ class _MainScreenState extends AuthRequiredState with Loadable { } Future takeVideo() async { + setState(() { + isRecording = false; + }); + if (!controller!.value.isRecordingVideo) { // Recording has already been stopped return; } setState(() { - isRecording = false; + lockCamera = true; }); - context.showPendingSnackBar(message: 'Saving video...'); - - final file = File((await controller!.stopVideoRecording()).path); - - context.showPendingSnackBar(message: 'Uploading video...'); - try { - await FileManager.uploadFile(_user, file); - } catch (error) { - if (mounted) { - context.showErrorSnackBar(message: error.toString()); - } - return; - } + context.showPendingSnackBar(message: 'Saving video...'); - context.showSuccessSnackBar(message: 'Video uploaded!'); + final file = File((await controller!.stopVideoRecording()).path); + + context.showPendingSnackBar(message: 'Uploading video...'); + + try { + await FileManager.uploadFile(_user, file); + } catch (error) { + if (mounted) { + context.showErrorSnackBar(message: error.toString()); + } + return; + } + + context.showSuccessSnackBar(message: 'Video uploaded!'); + } finally { + setState(() { + lockCamera = false; + }); + } if (mounted) { await getLastPhoto(); @@ -231,24 +254,25 @@ class _MainScreenState extends AuthRequiredState with Loadable { }, ), CameraButton( + disabled: lockCamera, active: isRecording, onVideoBegin: () async { + setState(() { + isRecording = true; + }); + if (controller!.value.isRecordingVideo) { // A recording has already started, do nothing. return; } - setState(() { - isRecording = true; - }); - await controller!.startVideoRecording(); }, onVideoEnd: takeVideo, onPhotoShot: takePhoto, ), lastPhoto == null - ? TodayPhotoButton() + ? const TodayPhotoButton() : TodayPhotoButton( data: lastPhoto![0], type: lastPhoto![1], @@ -266,7 +290,7 @@ class _MainScreenState extends AuthRequiredState with Loadable { uploadingPhotoAnimation = null; }); }, - ) + ), ], ), ); diff --git a/lib/widgets/camera_button.dart b/lib/widgets/camera_button.dart index 6baa80b..62dcd90 100644 --- a/lib/widgets/camera_button.dart +++ b/lib/widgets/camera_button.dart @@ -22,8 +22,11 @@ class CameraButton extends StatefulWidget { State createState() => _CameraButtonState(); } +const OUT_DURATION = Duration(milliseconds: 300); + class _CameraButtonState extends State { - bool shrinkIcon = false; + bool animateToVideoIcon = false; + bool videoInAnimationActive = false; @override Widget build(BuildContext context) { @@ -34,7 +37,8 @@ class _CameraButtonState extends State { } setState(() { - shrinkIcon = false; + videoInAnimationActive = false; + animateToVideoIcon = false; }); HapticFeedback.heavyImpact(); @@ -51,7 +55,7 @@ class _CameraButtonState extends State { } setState(() { - shrinkIcon = true; + animateToVideoIcon = true; }); }, onLongPressUp: () { @@ -60,7 +64,8 @@ class _CameraButtonState extends State { } setState(() { - shrinkIcon = false; + videoInAnimationActive = false; + animateToVideoIcon = false; }); if (widget.active) { @@ -74,6 +79,10 @@ class _CameraButtonState extends State { HapticFeedback.heavyImpact(); + setState(() { + videoInAnimationActive = true; + }); + if (widget.active) { widget.onVideoEnd(); } else { @@ -84,41 +93,47 @@ class _CameraButtonState extends State { opacity: widget.disabled ? 0.5 : 1.0, child: Stack( alignment: Alignment.center, - children: widget.active - ? const [ - Icon( - Icons.circle, - size: 75, - color: Colors.white, - ), - Icon( - Icons.circle, - size: 65, - color: Colors.red, - ), - Icon( - Icons.stop, - size: 45, - color: Colors.white, - ), - ] - : [ - Icon( - Icons.circle, - size: 75, - color: Colors.white.withOpacity(.2), - ), - AnimatedScale( - duration: kLongPressTimeout, - curve: Curves.easeInOut, - scale: shrinkIcon ? .8 : 1, - child: const Icon( - Icons.circle, - size: 50, - color: Colors.white, - ), - ), - ], + children: [ + Icon( + Icons.circle, + size: 75, + color: Colors.white.withOpacity(.2), + ), + AnimatedScale( + duration: animateToVideoIcon ? kLongPressTimeout : OUT_DURATION, + curve: Curves.easeInOut, + scale: animateToVideoIcon ? (75 / 50) : 1, + child: const Icon( + Icons.circle, + size: 50, + color: Colors.white, + ), + ), + AnimatedScale( + curve: Curves.easeInOut, + duration: animateToVideoIcon + ? const Duration(milliseconds: 180) + : OUT_DURATION, + scale: videoInAnimationActive ? 1 : 0, + child: const Icon( + Icons.circle, + size: 65, + color: Colors.red, + ), + ), + AnimatedScale( + curve: Curves.easeOutCirc, + duration: animateToVideoIcon + ? const Duration(milliseconds: 1000) + : OUT_DURATION, + scale: videoInAnimationActive ? 1 : .6, + child: const Icon( + Icons.stop, + size: 45, + color: Colors.white, + ), + ), + ], ), ), );