diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index eab661a9c..068e9964a 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -4,7 +4,7 @@ import 'package:aves/model/image_file_service.dart'; import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_service.dart'; import 'package:aves/utils/change_notifier.dart'; -import 'package:aves/utils/date_utils.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:geocoder/geocoder.dart'; import 'package:path/path.dart'; diff --git a/lib/utils/date_utils.dart b/lib/utils/time_utils.dart similarity index 100% rename from lib/utils/date_utils.dart rename to lib/utils/time_utils.dart diff --git a/lib/widgets/album/sections.dart b/lib/widgets/album/sections.dart index 1e18b747c..7c4b662b5 100644 --- a/lib/widgets/album/sections.dart +++ b/lib/widgets/album/sections.dart @@ -1,4 +1,4 @@ -import 'package:aves/utils/date_utils.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/common/outlined_text.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/album/thumbnail_collection.dart index 800d62831..896f48fb1 100644 --- a/lib/widgets/album/thumbnail_collection.dart +++ b/lib/widgets/album/thumbnail_collection.dart @@ -3,7 +3,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/widgets/album/sections.dart'; import 'package:aves/widgets/album/thumbnail.dart'; import 'package:aves/widgets/common/icons.dart'; -import 'package:aves/widgets/fullscreen/image_page.dart'; +import 'package:aves/widgets/fullscreen/fullscreen_page.dart'; import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; diff --git a/lib/widgets/fullscreen/fullscreen_action_delegate.dart b/lib/widgets/fullscreen/fullscreen_action_delegate.dart new file mode 100644 index 000000000..3400066c1 --- /dev/null +++ b/lib/widgets/fullscreen/fullscreen_action_delegate.dart @@ -0,0 +1,144 @@ +import 'dart:io'; + +import 'package:aves/model/image_collection.dart'; +import 'package:aves/model/image_entry.dart'; +import 'package:aves/utils/android_app_service.dart'; +import 'package:flushbar/flushbar.dart'; +import 'package:flutter/material.dart'; +import 'package:pdf/widgets.dart' as pdf; +import 'package:printing/printing.dart'; + +enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share } + +class FullscreenActionDelegate { + final ImageCollection collection; + final VoidCallback showInfo; + + FullscreenActionDelegate({ + @required this.collection, + @required this.showInfo, + }); + + onActionSelected(BuildContext context, ImageEntry entry, FullscreenAction action) { + switch (action) { + case FullscreenAction.delete: + _showDeleteDialog(context, entry); + break; + case FullscreenAction.edit: + AndroidAppService.edit(entry.uri, entry.mimeType); + break; + case FullscreenAction.info: + showInfo(); + break; + case FullscreenAction.rename: + _showRenameDialog(context, entry); + break; + case FullscreenAction.open: + AndroidAppService.open(entry.uri, entry.mimeType); + break; + case FullscreenAction.openMap: + AndroidAppService.openMap(entry.geoUri); + break; + case FullscreenAction.print: + _print(entry); + break; + case FullscreenAction.rotateCCW: + _rotate(context, entry, clockwise: false); + break; + case FullscreenAction.rotateCW: + _rotate(context, entry, clockwise: true); + break; + case FullscreenAction.setAs: + AndroidAppService.setAs(entry.uri, entry.mimeType); + break; + case FullscreenAction.share: + AndroidAppService.share(entry.uri, entry.mimeType); + break; + } + } + + _showFeedback(BuildContext context, String message) { + Flushbar( + message: message, + margin: EdgeInsets.all(8), + borderRadius: 8, + borderColor: Colors.white30, + borderWidth: 0.5, + duration: Duration(seconds: 2), + flushbarPosition: FlushbarPosition.TOP, + animationDuration: Duration(milliseconds: 600), + )..show(context); + } + + _print(ImageEntry entry) async { + final doc = pdf.Document(title: entry.title); + final image = await pdfImageFromImageProvider( + pdf: doc.document, + image: FileImage(File(entry.path)), + ); + doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page + Printing.layoutPdf( + onLayout: (format) => doc.save(), + name: entry.title, + ); + } + + _rotate(BuildContext context, ImageEntry entry, {@required bool clockwise}) async { + final success = await entry.rotate(clockwise: clockwise); + _showFeedback(context, success ? 'Done!' : 'Failed'); + } + + _showDeleteDialog(BuildContext context, ImageEntry entry) async { + final confirmed = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text('Are you sure?'), + actions: [ + FlatButton( + onPressed: () => Navigator.pop(context), + child: Text('CANCEL'), + ), + FlatButton( + onPressed: () => Navigator.pop(context, true), + child: Text('DELETE'), + ), + ], + ); + }, + ); + if (confirmed == null || !confirmed) return; + if (!await collection.delete(entry)) { + _showFeedback(context, 'Failed'); + } else if (collection.sortedEntries.isEmpty) { + Navigator.pop(context); + } + } + + _showRenameDialog(BuildContext context, ImageEntry entry) async { + final currentName = entry.title; + final controller = TextEditingController(text: currentName); + final newName = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: TextField( + controller: controller, + autofocus: true, + ), + actions: [ + FlatButton( + onPressed: () => Navigator.pop(context), + child: Text('CANCEL'), + ), + FlatButton( + onPressed: () => Navigator.pop(context, controller.text), + child: Text('APPLY'), + ), + ], + ); + }); + if (newName == null || newName.isEmpty) return; + _showFeedback(context, await entry.rename(newName) ? 'Done!' : 'Failed'); + } +} diff --git a/lib/widgets/fullscreen/fullscreen_page.dart b/lib/widgets/fullscreen/fullscreen_page.dart new file mode 100644 index 000000000..001b505cb --- /dev/null +++ b/lib/widgets/fullscreen/fullscreen_page.dart @@ -0,0 +1,276 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:aves/model/image_collection.dart'; +import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart'; +import 'package:aves/widgets/fullscreen/image_page.dart'; +import 'package:aves/widgets/fullscreen/info/info_page.dart'; +import 'package:aves/widgets/fullscreen/overlay/bottom.dart'; +import 'package:aves/widgets/fullscreen/overlay/top.dart'; +import 'package:aves/widgets/fullscreen/overlay/video.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:screen/screen.dart'; +import 'package:tuple/tuple.dart'; +import 'package:video_player/video_player.dart'; + +class FullscreenPage extends AnimatedWidget { + final ImageCollection collection; + final String initialUri; + + const FullscreenPage({ + Key key, + this.collection, + this.initialUri, + }) : super(key: key, listenable: collection); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: FullscreenBody( + collection: collection, + initialUri: initialUri, + ), + resizeToAvoidBottomInset: false, +// Hero( +// tag: uri, +// child: Stack( +// children: [ +// Center( +// child: widget.thumbnail == null +// ? CircularProgressIndicator() +// : Image.memory( +// widget.thumbnail, +// width: requestWidth, +// height: requestHeight, +// fit: BoxFit.contain, +// ), +// ), +// Center( +// child: FadeInImage( +// placeholder: MemoryImage(kTransparentImage), +// image: FileImage(File(path)), +// fadeOutDuration: Duration(milliseconds: 1), +// fadeInDuration: Duration(milliseconds: 200), +// width: requestWidth, +// height: requestHeight, +// fit: BoxFit.contain, +// ), +// ), +// ], +// ), +// ), + ); + } +} + +class FullscreenBody extends StatefulWidget { + final ImageCollection collection; + final String initialUri; + + const FullscreenBody({ + Key key, + this.collection, + this.initialUri, + }) : super(key: key); + + @override + FullscreenBodyState createState() => FullscreenBodyState(); +} + +class FullscreenBodyState extends State with SingleTickerProviderStateMixin { + bool _isInitialScale = true; + int _currentHorizontalPage, _currentVerticalPage = 0; + PageController _horizontalPager, _verticalPager; + ValueNotifier _overlayVisible = ValueNotifier(true); + AnimationController _overlayAnimationController; + Animation _topOverlayScale; + Animation _bottomOverlayOffset; + EdgeInsets _frozenViewInsets, _frozenViewPadding; + FullscreenActionDelegate _actionDelegate; + List> _videoControllers = List(); + + ImageCollection get collection => widget.collection; + + List get entries => widget.collection.sortedEntries; + + @override + void initState() { + super.initState(); + final index = entries.indexWhere((entry) => entry.uri == widget.initialUri); + _currentHorizontalPage = max(0, index); + _horizontalPager = PageController(initialPage: _currentHorizontalPage); + _verticalPager = PageController(initialPage: _currentVerticalPage); + _overlayAnimationController = AnimationController( + duration: Duration(milliseconds: 400), + vsync: this, + ); + _topOverlayScale = CurvedAnimation( + parent: _overlayAnimationController, + curve: Curves.easeOutQuart, + ); + _bottomOverlayOffset = Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(CurvedAnimation( + parent: _overlayAnimationController, + curve: Curves.easeOutQuart, + )); + _overlayVisible.addListener(onOverlayVisibleChange); + _actionDelegate = FullscreenActionDelegate( + collection: collection, + showInfo: () => goToVerticalPage(1), + ); + initVideoController(); + + Screen.keepOn(true); + initOverlay(); + } + + initOverlay() async { + // wait for MaterialPageRoute.transitionDuration + // to show overlay after hero animation is complete + await Future.delayed(Duration(milliseconds: 300)); + onOverlayVisibleChange(); + } + + @override + void dispose() { + _overlayVisible.removeListener(onOverlayVisibleChange); + _videoControllers.forEach((kv) => kv.item2.dispose()); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final entry = _currentHorizontalPage != null && _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; + return WillPopScope( + onWillPop: () { + if (_currentVerticalPage == 1) { + goToVerticalPage(0); + return Future.value(false); + } + Screen.keepOn(false); + SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); + return Future.value(true); + }, + child: Stack( + children: [ + PageView( + scrollDirection: Axis.vertical, + controller: _verticalPager, + physics: _isInitialScale ? PageScrollPhysics() : NeverScrollableScrollPhysics(), + onPageChanged: (page) => setState(() => _currentVerticalPage = page), + children: [ + ImagePage( + collection: collection, + pageController: _horizontalPager, + onTap: () => _overlayVisible.value = !_overlayVisible.value, + onPageChanged: onHorizontalPageChanged, + onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial), + videoControllers: _videoControllers, + ), + NotificationListener( + onNotification: (notification) { + if (notification is BackUpNotification) goToVerticalPage(0); + return false; + }, + child: InfoPage(collection: collection, entry: entry), + ), + ], + ), + ..._buildOverlay(entry) + ], + ), + ); + } + + List _buildOverlay(ImageEntry entry) { + if (entry == null || _currentVerticalPage != 0) return []; + final videoController = entry.isVideo ? _videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null)?.item2 : null; + return [ + FullscreenTopOverlay( + entries: entries, + index: _currentHorizontalPage, + scale: _topOverlayScale, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + onActionSelected: (action) => _actionDelegate.onActionSelected(context, entry, action), + ), + Positioned( + bottom: 0, + child: Column( + children: [ + if (videoController != null) + VideoControlOverlay( + entry: entry, + controller: videoController, + scale: _topOverlayScale, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + ), + SlideTransition( + position: _bottomOverlayOffset, + child: FullscreenBottomOverlay( + entries: entries, + index: _currentHorizontalPage, + viewInsets: _frozenViewInsets, + viewPadding: _frozenViewPadding, + ), + ), + ], + ), + ) + ]; + } + + goToVerticalPage(int page) { + return _verticalPager.animateToPage( + page, + duration: Duration(milliseconds: 350), + curve: Curves.easeInOut, + ); + } + + onOverlayVisibleChange() async { + if (_overlayVisible.value) { + SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); + _overlayAnimationController.forward(); + } else { + final mediaQuery = MediaQuery.of(context); + _frozenViewInsets = mediaQuery.viewInsets; + _frozenViewPadding = mediaQuery.viewPadding; + SystemChrome.setEnabledSystemUIOverlays([]); + await _overlayAnimationController.reverse(); + _frozenViewInsets = null; + _frozenViewPadding = null; + } + } + + onHorizontalPageChanged(int page) { + _currentHorizontalPage = page; + pauseVideoControllers(); + initVideoController(); + setState(() {}); + } + + pauseVideoControllers() => _videoControllers.forEach((e) => e.item2.pause()); + + initVideoController() { + final entry = _currentHorizontalPage != null && _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; + if (entry == null || !entry.isVideo) return; + + final path = entry.path; + var controllerEntry = _videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null); + if (controllerEntry != null) { + _videoControllers.remove(controllerEntry); + } else { + final controller = VideoPlayerController.file(File(path))..initialize(); + controllerEntry = Tuple2(path, controller); + } + _videoControllers.insert(0, controllerEntry); + while (_videoControllers.length > 3) { + _videoControllers.removeLast().item2.dispose(); + } + } +} diff --git a/lib/widgets/fullscreen/image_page.dart b/lib/widgets/fullscreen/image_page.dart index eff6a48b5..e20b6e12d 100644 --- a/lib/widgets/fullscreen/image_page.dart +++ b/lib/widgets/fullscreen/image_page.dart @@ -1,402 +1,14 @@ import 'dart:io'; -import 'dart:math'; import 'package:aves/model/image_collection.dart'; import 'package:aves/model/image_entry.dart'; -import 'package:aves/utils/android_app_service.dart'; -import 'package:aves/widgets/fullscreen/info/info_page.dart'; -import 'package:aves/widgets/fullscreen/overlay_bottom.dart'; -import 'package:aves/widgets/fullscreen/overlay_top.dart'; -import 'package:aves/widgets/fullscreen/overlay_video.dart'; import 'package:aves/widgets/fullscreen/video.dart'; -import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; -import 'package:pdf/widgets.dart' as pdf; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; -import 'package:printing/printing.dart'; -import 'package:screen/screen.dart'; import 'package:tuple/tuple.dart'; import 'package:video_player/video_player.dart'; -class FullscreenPage extends AnimatedWidget { - final ImageCollection collection; - final String initialUri; - - const FullscreenPage({ - Key key, - this.collection, - this.initialUri, - }) : super(key: key, listenable: collection); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: FullscreenBody( - collection: collection, - initialUri: initialUri, - ), - resizeToAvoidBottomInset: false, -// Hero( -// tag: uri, -// child: Stack( -// children: [ -// Center( -// child: widget.thumbnail == null -// ? CircularProgressIndicator() -// : Image.memory( -// widget.thumbnail, -// width: requestWidth, -// height: requestHeight, -// fit: BoxFit.contain, -// ), -// ), -// Center( -// child: FadeInImage( -// placeholder: MemoryImage(kTransparentImage), -// image: FileImage(File(path)), -// fadeOutDuration: Duration(milliseconds: 1), -// fadeInDuration: Duration(milliseconds: 200), -// width: requestWidth, -// height: requestHeight, -// fit: BoxFit.contain, -// ), -// ), -// ], -// ), -// ), - ); - } -} - -class FullscreenBody extends StatefulWidget { - final ImageCollection collection; - final String initialUri; - - const FullscreenBody({ - Key key, - this.collection, - this.initialUri, - }) : super(key: key); - - @override - FullscreenBodyState createState() => FullscreenBodyState(); -} - -class FullscreenBodyState extends State with SingleTickerProviderStateMixin { - bool _isInitialScale = true; - int _currentHorizontalPage, _currentVerticalPage = 0; - PageController _horizontalPager, _verticalPager; - ValueNotifier _overlayVisible = ValueNotifier(true); - AnimationController _overlayAnimationController; - Animation _topOverlayScale; - Animation _bottomOverlayOffset; - EdgeInsets _frozenViewInsets, _frozenViewPadding; - List> _videoControllers = List(); - - ImageCollection get collection => widget.collection; - - List get entries => widget.collection.sortedEntries; - - @override - void initState() { - super.initState(); - final index = entries.indexWhere((entry) => entry.uri == widget.initialUri); - _currentHorizontalPage = max(0, index); - _horizontalPager = PageController(initialPage: _currentHorizontalPage); - _verticalPager = PageController(initialPage: _currentVerticalPage); - _overlayAnimationController = AnimationController( - duration: Duration(milliseconds: 400), - vsync: this, - ); - _topOverlayScale = CurvedAnimation( - parent: _overlayAnimationController, - curve: Curves.easeOutQuart, - ); - _bottomOverlayOffset = Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(CurvedAnimation( - parent: _overlayAnimationController, - curve: Curves.easeOutQuart, - )); - _overlayVisible.addListener(onOverlayVisibleChange); - initVideoController(); - - Screen.keepOn(true); - initOverlay(); - } - - initOverlay() async { - // wait for MaterialPageRoute.transitionDuration - // to show overlay after hero animation is complete - await Future.delayed(Duration(milliseconds: 300)); - onOverlayVisibleChange(); - } - - @override - void dispose() { - _overlayVisible.removeListener(onOverlayVisibleChange); - _videoControllers.forEach((kv) => kv.item2.dispose()); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final entry = _currentHorizontalPage != null && _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; - return WillPopScope( - onWillPop: () { - if (_currentVerticalPage == 1) { - goToVerticalPage(0); - return Future.value(false); - } - Screen.keepOn(false); - SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); - return Future.value(true); - }, - child: Stack( - children: [ - PageView( - scrollDirection: Axis.vertical, - controller: _verticalPager, - physics: _isInitialScale ? PageScrollPhysics() : NeverScrollableScrollPhysics(), - onPageChanged: (page) => setState(() => _currentVerticalPage = page), - children: [ - ImagePage( - collection: collection, - pageController: _horizontalPager, - onTap: () => _overlayVisible.value = !_overlayVisible.value, - onPageChanged: onHorizontalPageChanged, - onScaleChanged: (state) => setState(() => _isInitialScale = state == PhotoViewScaleState.initial), - videoControllers: _videoControllers, - ), - NotificationListener( - onNotification: (notification) { - if (notification is BackUpNotification) goToVerticalPage(0); - return false; - }, - child: InfoPage(collection: collection, entry: entry), - ), - ], - ), - ..._buildOverlay(entry) - ], - ), - ); - } - - List _buildOverlay(ImageEntry entry) { - if (entry == null || _currentVerticalPage != 0) return []; - final videoController = entry.isVideo ? _videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null)?.item2 : null; - return [ - FullscreenTopOverlay( - entries: entries, - index: _currentHorizontalPage, - scale: _topOverlayScale, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - onActionSelected: (action) => onActionSelected(entry, action), - ), - Positioned( - bottom: 0, - child: Column( - children: [ - if (videoController != null) - VideoControlOverlay( - entry: entry, - controller: videoController, - scale: _topOverlayScale, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - ), - SlideTransition( - position: _bottomOverlayOffset, - child: FullscreenBottomOverlay( - entries: entries, - index: _currentHorizontalPage, - viewInsets: _frozenViewInsets, - viewPadding: _frozenViewPadding, - ), - ), - ], - ), - ) - ]; - } - - goToVerticalPage(int page) { - return _verticalPager.animateToPage( - page, - duration: Duration(milliseconds: 350), - curve: Curves.easeInOut, - ); - } - - onOverlayVisibleChange() async { - if (_overlayVisible.value) { - SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); - _overlayAnimationController.forward(); - } else { - final mediaQuery = MediaQuery.of(context); - _frozenViewInsets = mediaQuery.viewInsets; - _frozenViewPadding = mediaQuery.viewPadding; - SystemChrome.setEnabledSystemUIOverlays([]); - await _overlayAnimationController.reverse(); - _frozenViewInsets = null; - _frozenViewPadding = null; - } - } - - onActionSelected(ImageEntry entry, FullscreenAction action) { - switch (action) { - case FullscreenAction.delete: - showDeleteDialog(entry); - break; - case FullscreenAction.edit: - AndroidAppService.edit(entry.uri, entry.mimeType); - break; - case FullscreenAction.info: - goToVerticalPage(1); - break; - case FullscreenAction.rename: - showRenameDialog(entry); - break; - case FullscreenAction.open: - AndroidAppService.open(entry.uri, entry.mimeType); - break; - case FullscreenAction.openMap: - AndroidAppService.openMap(entry.geoUri); - break; - case FullscreenAction.print: - print(entry); - break; - case FullscreenAction.rotateCCW: - rotate(entry, clockwise: false); - break; - case FullscreenAction.rotateCW: - rotate(entry, clockwise: true); - break; - case FullscreenAction.setAs: - AndroidAppService.setAs(entry.uri, entry.mimeType); - break; - case FullscreenAction.share: - AndroidAppService.share(entry.uri, entry.mimeType); - break; - } - } - - showFeedback(String message) { - Flushbar( - message: message, - margin: EdgeInsets.all(8), - borderRadius: 8, - borderColor: Colors.white30, - borderWidth: 0.5, - duration: Duration(seconds: 2), - flushbarPosition: FlushbarPosition.TOP, - animationDuration: Duration(milliseconds: 600), - )..show(context); - } - - print(ImageEntry entry) async { - final doc = pdf.Document(title: entry.title); - final image = await pdfImageFromImageProvider( - pdf: doc.document, - image: FileImage(File(entry.path)), - ); - doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page - Printing.layoutPdf( - onLayout: (format) => doc.save(), - name: entry.title, - ); - } - - rotate(ImageEntry entry, {@required bool clockwise}) async { - final success = await entry.rotate(clockwise: clockwise); - showFeedback(success ? 'Done!' : 'Failed'); - } - - showDeleteDialog(ImageEntry entry) async { - final confirmed = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: Text('Are you sure?'), - actions: [ - FlatButton( - onPressed: () => Navigator.pop(context), - child: Text('CANCEL'), - ), - FlatButton( - onPressed: () => Navigator.pop(context, true), - child: Text('DELETE'), - ), - ], - ); - }, - ); - if (confirmed == null || !confirmed) return; - if (!await collection.delete(entry)) { - showFeedback('Failed'); - } else if (entries.isEmpty) { - Navigator.pop(context); - } - } - - showRenameDialog(ImageEntry entry) async { - final currentName = entry.title; - final controller = TextEditingController(text: currentName); - final newName = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - content: TextField( - controller: controller, - autofocus: true, - ), - actions: [ - FlatButton( - onPressed: () => Navigator.pop(context), - child: Text('CANCEL'), - ), - FlatButton( - onPressed: () => Navigator.pop(context, controller.text), - child: Text('APPLY'), - ), - ], - ); - }); - if (newName == null || newName.isEmpty) return; - showFeedback(await entry.rename(newName) ? 'Done!' : 'Failed'); - } - - onHorizontalPageChanged(int page) { - _currentHorizontalPage = page; - initVideoController(); - setState(() {}); - } - - initVideoController() { - final entry = _currentHorizontalPage != null && _currentHorizontalPage < entries.length ? entries[_currentHorizontalPage] : null; - if (entry == null || !entry.isVideo) return; - - final path = entry.path; - var controllerEntry = _videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null); - if (controllerEntry != null) { - _videoControllers.remove(controllerEntry); - } else { - final controller = VideoPlayerController.file(File(path))..initialize(); - controllerEntry = Tuple2(path, controller); - } - _videoControllers.insert(0, controllerEntry); - while (_videoControllers.length > 3) { - _videoControllers.removeLast().item2.dispose(); - } - } -} - -enum FullscreenAction { delete, edit, info, open, openMap, print, rename, rotateCCW, rotateCW, setAs, share } - class ImagePage extends StatefulWidget { final ImageCollection collection; final PageController pageController; diff --git a/lib/widgets/fullscreen/overlay_bottom.dart b/lib/widgets/fullscreen/overlay/bottom.dart similarity index 100% rename from lib/widgets/fullscreen/overlay_bottom.dart rename to lib/widgets/fullscreen/overlay/bottom.dart diff --git a/lib/widgets/fullscreen/overlay/common.dart b/lib/widgets/fullscreen/overlay/common.dart new file mode 100644 index 000000000..cdc964131 --- /dev/null +++ b/lib/widgets/fullscreen/overlay/common.dart @@ -0,0 +1,29 @@ +import 'package:aves/widgets/common/blurred.dart'; +import 'package:flutter/material.dart'; + +class OverlayButton extends StatelessWidget { + final Animation scale; + final Widget child; + + const OverlayButton({Key key, this.scale, this.child}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ScaleTransition( + scale: scale, + child: BlurredOval( + child: Material( + type: MaterialType.circle, + color: Colors.black26, + child: Ink( + decoration: BoxDecoration( + border: Border.all(color: Colors.white30, width: 0.5), + shape: BoxShape.circle, + ), + child: child, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/fullscreen/overlay_top.dart b/lib/widgets/fullscreen/overlay/top.dart similarity index 82% rename from lib/widgets/fullscreen/overlay_top.dart rename to lib/widgets/fullscreen/overlay/top.dart index efa56c953..576ada268 100644 --- a/lib/widgets/fullscreen/overlay_top.dart +++ b/lib/widgets/fullscreen/overlay/top.dart @@ -1,7 +1,7 @@ import 'package:aves/model/image_entry.dart'; -import 'package:aves/widgets/common/blurred.dart'; import 'package:aves/widgets/common/menu_row.dart'; -import 'package:aves/widgets/fullscreen/image_page.dart'; +import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart'; +import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:flutter/material.dart'; class FullscreenTopOverlay extends StatelessWidget { @@ -109,30 +109,3 @@ class FullscreenTopOverlay extends StatelessWidget { ); } } - -class OverlayButton extends StatelessWidget { - final Animation scale; - final Widget child; - - const OverlayButton({Key key, this.scale, this.child}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ScaleTransition( - scale: scale, - child: BlurredOval( - child: Material( - type: MaterialType.circle, - color: Colors.black26, - child: Ink( - decoration: BoxDecoration( - border: Border.all(color: Colors.white30, width: 0.5), - shape: BoxShape.circle, - ), - child: child, - ), - ), - ), - ); - } -} diff --git a/lib/widgets/fullscreen/overlay_video.dart b/lib/widgets/fullscreen/overlay/video.dart similarity index 97% rename from lib/widgets/fullscreen/overlay_video.dart rename to lib/widgets/fullscreen/overlay/video.dart index d6f0f7e85..eafa6e183 100644 --- a/lib/widgets/fullscreen/overlay_video.dart +++ b/lib/widgets/fullscreen/overlay/video.dart @@ -1,9 +1,8 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/android_app_service.dart'; -import 'package:aves/utils/date_utils.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/common/blurred.dart'; -import 'package:aves/widgets/fullscreen/overlay_top.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart';