diff --git a/lib/locale/l10n/app_de.arb b/lib/locale/l10n/app_de.arb index 90928b3..cca119d 100644 --- a/lib/locale/l10n/app_de.arb +++ b/lib/locale/l10n/app_de.arb @@ -111,6 +111,7 @@ "settingsScreenGeneralSectionTitle": "General", "settingsScreenGeneralSectionQualityLabel": "Qualität", "settingsScreenGeneralSectionAskForMemoryAnnotationsLabel": "Nach Anmerkungen für Erinnerungen fragen", + "settingsScreenGeneralSectionStartRecordingOnStartupLabel": "Aufnahme automatisch beim Öffnen der App starten", "settingsScreenResetHelpSheetsLabel": "Hilf-Sheets zurücksetzen", "settingsScreenResetHelpSheetsResetSuccessfully": "Hilf-Sheets wurden zurückgesetzt.", diff --git a/lib/locale/l10n/app_en.arb b/lib/locale/l10n/app_en.arb index 409d1ec..bec224e 100644 --- a/lib/locale/l10n/app_en.arb +++ b/lib/locale/l10n/app_en.arb @@ -161,6 +161,7 @@ "settingsScreenGeneralSectionTitle": "General", "settingsScreenGeneralSectionQualityLabel": "Quality", "settingsScreenGeneralSectionAskForMemoryAnnotationsLabel": "Ask for memory annotations", + "settingsScreenGeneralSectionStartRecordingOnStartupLabel": "Automatically start recording on startup", "settingsScreenResetHelpSheetsLabel": "Reset Help Sheets", "settingsScreenResetHelpSheetsResetSuccessfully": "Help Sheets reset successfully.", diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 268f35c..3020ba8 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -5,25 +5,31 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:quid_faciam_hodie/constants/storage_keys.dart'; +import 'package:quid_faciam_hodie/utils/string_to_bool.dart'; const secure = FlutterSecureStorage(); class Settings extends ChangeNotifier { ResolutionPreset _resolution = ResolutionPreset.max; bool _askForMemoryAnnotations = false; + bool _recordOnStartup = false; - Settings({ - final ResolutionPreset? resolution, - final bool? askForMemoryAnnotations, - }) : _resolution = resolution ?? ResolutionPreset.max, - _askForMemoryAnnotations = askForMemoryAnnotations ?? true; + Settings( + {final ResolutionPreset? resolution, + final bool? askForMemoryAnnotations, + final bool? recordOnStartup}) + : _resolution = resolution ?? ResolutionPreset.max, + _askForMemoryAnnotations = askForMemoryAnnotations ?? true, + _recordOnStartup = recordOnStartup ?? false; ResolutionPreset get resolution => _resolution; bool get askForMemoryAnnotations => _askForMemoryAnnotations; + bool get recordOnStartup => _recordOnStartup; Map toJSONData() => { 'resolution': _resolution.toString(), 'askForMemoryAnnotations': _askForMemoryAnnotations ? 'true' : 'false', + 'recordOnStartup': _recordOnStartup ? 'true' : 'false', }; Future save() async { @@ -46,20 +52,14 @@ class Settings extends ChangeNotifier { final resolution = ResolutionPreset.values.firstWhereOrNull( (preset) => preset.toString() == data['resolution'], ); - final askForMemoryAnnotations = () { - switch (data['askForMemoryAnnotations']) { - case 'true': - return true; - case 'false': - return false; - default: - return null; - } - }(); + final askForMemoryAnnotations = + stringToBool(data['askForMemoryAnnotations']); + final recordOnStartup = stringToBool(data['recordOnStartup']); return Settings( resolution: resolution, askForMemoryAnnotations: askForMemoryAnnotations, + recordOnStartup: recordOnStartup, ); } @@ -74,4 +74,10 @@ class Settings extends ChangeNotifier { notifyListeners(); save(); } + + void setRecordOnStartup(final bool recordOnStartup) { + _recordOnStartup = recordOnStartup; + notifyListeners(); + save(); + } } diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 7185ad3..1f0adcc 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -20,6 +20,7 @@ import 'package:quid_faciam_hodie/managers/global_values_manager.dart'; import 'package:quid_faciam_hodie/models/memories.dart'; import 'package:quid_faciam_hodie/screens/main_screen/annotation_dialog.dart'; import 'package:quid_faciam_hodie/screens/main_screen/camera_help_content.dart'; +import 'package:quid_faciam_hodie/screens/main_screen/cancel_recording_button.dart'; import 'package:quid_faciam_hodie/screens/main_screen/settings_button_overlay.dart'; import 'package:quid_faciam_hodie/utils/auth_required.dart'; import 'package:quid_faciam_hodie/utils/loadable.dart'; @@ -49,6 +50,7 @@ class MainScreen extends StatefulWidget { class _MainScreenState extends AuthRequiredState with Loadable { int currentZoomLevelIndex = 0; + bool hasRecordedOnStartup = false; bool isRecording = false; bool lockCamera = false; bool isTorchEnabled = false; @@ -148,6 +150,19 @@ class _MainScreenState extends AuthRequiredState with Loadable { } } + Future startRecording() async { + setState(() { + isRecording = true; + }); + + if (controller!.value.isRecordingVideo) { + // A recording has already started, do nothing. + return; + } + + await controller!.startVideoRecording(); + } + void onNewCameraSelected(final CameraDescription cameraDescription) async { final settings = GlobalValuesManager.settings!; final previousCameraController = controller; @@ -179,6 +194,12 @@ class _MainScreenState extends AuthRequiredState with Loadable { await controller!.initialize(); await controller!.prepareForVideoRecording(); + if (settings.recordOnStartup && !hasRecordedOnStartup) { + startRecording(); + + hasRecordedOnStartup = true; + } + await determineZoomLevels(); if (!mounted) { @@ -420,7 +441,7 @@ class _MainScreenState extends AuthRequiredState with Loadable { controller!.buildPreview(), if (isRecording) RecordingOverlay(controller: controller!), - if (!isRecording) SettingsButtonOverlay(), + if (!isRecording) const SettingsButtonOverlay(), if (uploadingPhotoAnimation != null) UploadingPhoto( data: uploadingPhotoAnimation!, @@ -465,22 +486,34 @@ class _MainScreenState extends AuthRequiredState with Loadable { SECONDARY_BUTTONS_DURATION_MULTIPLIER, opacityDuration: DEFAULT_OPACITY_DURATION * SECONDARY_BUTTONS_DURATION_MULTIPLIER, - child: ChangeCameraButton( - disabled: lockCamera || isRecording, - onChangeCamera: () { - final currentCameraIndex = - GlobalValuesManager.cameras - .indexOf(controller!.description); - final availableCameras = - GlobalValuesManager.cameras.length; + child: isRecording + ? CancelRecordingButton( + onCancel: () { + setState(() { + isRecording = false; + }); - onNewCameraSelected( - GlobalValuesManager.cameras[ - (currentCameraIndex + 1) % - availableCameras], - ); - }, - ), + controller!.stopVideoRecording(); + }, + ) + : ChangeCameraButton( + disabled: lockCamera || isRecording, + onChangeCamera: () { + final currentCameraIndex = + GlobalValuesManager.cameras + .indexOf( + controller!.description); + final availableCameras = + GlobalValuesManager + .cameras.length; + + onNewCameraSelected( + GlobalValuesManager.cameras[ + (currentCameraIndex + 1) % + availableCameras], + ); + }, + ), ), ), Expanded( @@ -488,18 +521,7 @@ class _MainScreenState extends AuthRequiredState with Loadable { child: RecordButton( disabled: lockCamera, active: isRecording, - onVideoBegin: () async { - setState(() { - isRecording = true; - }); - - if (controller!.value.isRecordingVideo) { - // A recording has already started, do nothing. - return; - } - - await controller!.startVideoRecording(); - }, + onVideoBegin: startRecording, onVideoEnd: takeVideo, onPhotoShot: takePhoto, ), diff --git a/lib/screens/main_screen/cancel_recording_button.dart b/lib/screens/main_screen/cancel_recording_button.dart new file mode 100644 index 0000000..dc3b0b1 --- /dev/null +++ b/lib/screens/main_screen/cancel_recording_button.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; + +class CancelRecordingButton extends StatelessWidget { + final VoidCallback onCancel; + + const CancelRecordingButton({ + Key? key, + required this.onCancel, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + HapticFeedback.heavyImpact(); + onCancel(); + }, + child: Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.circle, + size: 60, + color: Colors.white.withOpacity(.2), + ), + Icon( + context.platformIcons.clear, + size: 25, + color: Colors.white, + ), + ], + ), + ); + } +} diff --git a/lib/screens/main_screen/record_button.dart b/lib/screens/main_screen/record_button.dart index 2256379..3239816 100644 --- a/lib/screens/main_screen/record_button.dart +++ b/lib/screens/main_screen/record_button.dart @@ -26,15 +26,12 @@ const OUT_DURATION = Duration(milliseconds: 300); class _RecordButtonState extends State { bool animateToVideoIcon = false; - bool videoInAnimationActive = false; - void cancelAnimation() { - if (videoInAnimationActive) { + if (widget.active) { return; } setState(() { - videoInAnimationActive = false; animateToVideoIcon = false; }); } @@ -49,7 +46,6 @@ class _RecordButtonState extends State { } setState(() { - videoInAnimationActive = false; animateToVideoIcon = false; }); @@ -69,7 +65,6 @@ class _RecordButtonState extends State { setState(() { animateToVideoIcon = false; - videoInAnimationActive = true; }); HapticFeedback.heavyImpact(); @@ -87,7 +82,6 @@ class _RecordButtonState extends State { } setState(() { - videoInAnimationActive = false; animateToVideoIcon = false; }); @@ -117,19 +111,18 @@ class _RecordButtonState extends State { alignment: Alignment.center, children: [ AnimatedContainer( - duration: videoInAnimationActive ? Duration.zero : OUT_DURATION, + duration: widget.active ? Duration.zero : OUT_DURATION, width: 60, height: 60, decoration: BoxDecoration( - color: videoInAnimationActive - ? Colors.white - : Colors.white.withOpacity(.2), + color: + widget.active ? Colors.white : Colors.white.withOpacity(.2), borderRadius: BorderRadius.circular(30), ), ), AnimatedScale( duration: () { - if (videoInAnimationActive) { + if (widget.active) { return Duration(milliseconds: 400); } @@ -141,7 +134,7 @@ class _RecordButtonState extends State { }(), curve: Curves.easeInOut, scale: () { - if (videoInAnimationActive) { + if (widget.active) { return .6; } @@ -153,7 +146,7 @@ class _RecordButtonState extends State { }(), child: AnimatedContainer( duration: () { - if (videoInAnimationActive) { + if (widget.active) { return Duration(milliseconds: 400); } @@ -166,8 +159,8 @@ class _RecordButtonState extends State { width: 40, height: 40, decoration: BoxDecoration( - color: videoInAnimationActive ? Colors.red : Colors.white, - borderRadius: videoInAnimationActive + color: widget.active ? Colors.red : Colors.white, + borderRadius: widget.active ? BorderRadius.circular(8) : BorderRadius.circular(50), ), diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 4b9f327..0c6003e 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -218,6 +218,12 @@ class _SettingsScreenState extends AuthRequiredState .settingsScreenGeneralSectionAskForMemoryAnnotationsLabel, ), ), + SettingsTile.switchTile( + initialValue: settings.recordOnStartup, + onToggle: settings.setRecordOnStartup, + title: Text(localizations + .settingsScreenGeneralSectionStartRecordingOnStartupLabel), + ), SettingsTile( leading: Icon(context.platformIcons.help), title: Text( diff --git a/lib/utils/string_to_bool.dart b/lib/utils/string_to_bool.dart new file mode 100644 index 0000000..ba847c8 --- /dev/null +++ b/lib/utils/string_to_bool.dart @@ -0,0 +1,10 @@ +bool? stringToBool(final String value) { + switch (value) { + case 'true': + return true; + case 'false': + return false; + default: + return null; + } +}