import 'dart:async'; import 'package:aves/model/actions/video_actions.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/services/services.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/action_mixins/permission_aware.dart'; import 'package:aves/widgets/common/action_mixins/size_aware.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/video_speed_dialog.dart'; import 'package:aves/widgets/dialogs/video_stream_selection_dialog.dart'; import 'package:aves/widgets/viewer/video/controller.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:pedantic/pedantic.dart'; import 'package:provider/provider.dart'; class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMixin { final CollectionLens? collection; VideoActionDelegate({ required this.collection, }); void onActionSelected(BuildContext context, AvesVideoController controller, VideoAction action) { switch (action) { case VideoAction.captureFrame: _captureFrame(context, controller); break; case VideoAction.replay10: if (controller.isReady) controller.seekTo(controller.currentPosition - 10000); break; case VideoAction.selectStreams: _showStreamSelectionDialog(context, controller); break; case VideoAction.setSpeed: _showSpeedDialog(context, controller); break; case VideoAction.togglePlay: _togglePlayPause(context, controller); break; } } Future _captureFrame(BuildContext context, AvesVideoController controller) async { final positionMillis = controller.currentPosition; final bytes = await controller.captureFrame(); final destinationAlbum = androidFileUtils.videoCapturesPath; if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return; if (!await checkFreeSpace(context, bytes.length, destinationAlbum)) return; final entry = controller.entry; final rotationDegrees = entry.rotationDegrees; final dateTimeMillis = entry.catalogMetadata?.dateMillis; final latLng = entry.latLng; final exif = { if (rotationDegrees != 0) 'rotationDegrees': rotationDegrees, if (dateTimeMillis != null && dateTimeMillis != 0) 'dateTimeMillis': dateTimeMillis, if (latLng != null) ...{ 'latitude': latLng.latitude, 'longitude': latLng.longitude, } }; final newFields = await imageFileService.captureFrame( entry, desiredName: '${entry.bestTitle}_${'$positionMillis'.padLeft(8, '0')}', exif: exif, bytes: bytes, destinationAlbum: destinationAlbum, ); final success = newFields.isNotEmpty; if (success) { final _collection = collection; final showAction = _collection != null ? SnackBarAction( label: context.l10n.showButtonLabel, onPressed: () async { final highlightInfo = context.read(); final source = _collection.source; final targetCollection = CollectionLens( source: source, filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))}, groupFactor: _collection.groupFactor, sortFactor: _collection.sortFactor, ); unawaited(Navigator.pushAndRemoveUntil( context, MaterialPageRoute( settings: const RouteSettings(name: CollectionPage.routeName), builder: (context) { return CollectionPage( targetCollection, ); }, ), (route) => false, )); await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay); final newUri = newFields['uri'] as String?; final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => entry.uri == newUri); if (targetEntry != null) { highlightInfo.trackItem(targetEntry, highlightItem: targetEntry); } }, ) : null; showFeedback(context, context.l10n.genericSuccessFeedback, showAction); } else { showFeedback(context, context.l10n.genericFailureFeedback); } } Future _showStreamSelectionDialog(BuildContext context, AvesVideoController controller) async { final selectedStreams = await showDialog>( context: context, builder: (context) => VideoStreamSelectionDialog( streams: controller.streams, ), ); if (selectedStreams == null || selectedStreams.isEmpty) return; // TODO TLAD [video] get stream list & guess default selected streams, when the controller is not initialized yet await Future.forEach>( selectedStreams.entries, (kv) => controller.selectStream(kv.key, kv.value), ); } Future _showSpeedDialog(BuildContext context, AvesVideoController controller) async { final newSpeed = await showDialog( context: context, builder: (context) => VideoSpeedDialog( current: controller.speed, min: controller.minSpeed, max: controller.maxSpeed, ), ); if (newSpeed == null) return; controller.speed = newSpeed; } Future _togglePlayPause(BuildContext context, AvesVideoController controller) async { if (controller.isPlaying) { await controller.pause(); } else { await controller.play(); } } }