diff --git a/lib/ui/album_widget.dart b/lib/ui/album_widget.dart index b7d7ea343..42b863244 100644 --- a/lib/ui/album_widget.dart +++ b/lib/ui/album_widget.dart @@ -19,7 +19,6 @@ class _AlbumPageState extends State { @override Widget build(Object context) { - Logger().i("Building with " + widget.album.photos.length.toString()); return Scaffold( appBar: GalleryAppBarWidget( widget.album.name, diff --git a/lib/ui/detail_page.dart b/lib/ui/detail_page.dart index eada570b7..9bd230704 100644 --- a/lib/ui/detail_page.dart +++ b/lib/ui/detail_page.dart @@ -1,76 +1,159 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; import 'package:myapp/core/lru_map.dart'; +import 'package:myapp/db/db_helper.dart'; import 'package:myapp/models/photo.dart'; +import 'package:myapp/photo_loader.dart'; import 'package:myapp/ui/zoomable_image.dart'; -import 'extents_page_view.dart'; +import 'package:photo_manager/photo_manager.dart'; +import 'package:provider/provider.dart'; import 'package:myapp/utils/share_util.dart'; class DetailPage extends StatefulWidget { final List photos; final int selectedIndex; + final Function(Photo) onPhotoDeleted; - DetailPage(this.photos, this.selectedIndex, {Key key}) : super(key: key); + DetailPage(this.photos, this.selectedIndex, {this.onPhotoDeleted, key}) + : super(key: key); @override _DetailPageState createState() => _DetailPageState(); } class _DetailPageState extends State { + PhotoLoader get photoLoader => Provider.of(context); bool _shouldDisableScroll = false; + List _photos; int _selectedIndex = 0; - final _cachedImages = LRUMap(5); + PageController _pageController; + LRUMap _cachedImages; + + @override + void initState() { + Logger().i("initState"); + _photos = widget.photos; + _selectedIndex = widget.selectedIndex; + _cachedImages = LRUMap(5); + super.initState(); + } @override Widget build(BuildContext context) { - _selectedIndex = widget.selectedIndex; - - Logger().i("Loading " + widget.photos[_selectedIndex].toString()); - var pageController = PageController(initialPage: _selectedIndex); + Logger().i("Opening " + + _selectedIndex.toString() + + " / " + + _photos.length.toString() + + "photos ."); return Scaffold( appBar: _buildAppBar(), body: Center( child: Container( - child: ExtentsPageView.extents( - itemBuilder: (context, index) { - if (_cachedImages.get(index) != null) { - return _cachedImages.get(index); - } - final image = ZoomableImage( - widget.photos[index], - shouldDisableScroll: (value) { - setState(() { - _shouldDisableScroll = value; - }); - }, - ); - _cachedImages.put(index, image); - return image; - }, - onPageChanged: (int index) { - _selectedIndex = index; - }, - physics: _shouldDisableScroll - ? NeverScrollableScrollPhysics() - : PageScrollPhysics(), - controller: pageController, - itemCount: widget.photos.length, - ), + child: _buildPageView(), ), ), ); } + PageView _buildPageView() { + _pageController = PageController(initialPage: _selectedIndex); + return PageView.builder( + itemBuilder: (context, index) { + final photo = _photos[index]; + if (_cachedImages.get(photo.generatedId) != null) { + return _cachedImages.get(photo.generatedId); + } + final image = ZoomableImage( + photo, + shouldDisableScroll: (value) { + setState(() { + _shouldDisableScroll = value; + }); + }, + ); + _cachedImages.put(photo.generatedId, image); + return image; + }, + onPageChanged: (int index) { + Logger().i("onPageChanged to " + index.toString()); + _selectedIndex = index; + }, + physics: _shouldDisableScroll + ? NeverScrollableScrollPhysics() + : PageScrollPhysics(), + controller: _pageController, + itemCount: _photos.length, + ); + } + AppBar _buildAppBar() { return AppBar( actions: [ + IconButton( + icon: Icon(Icons.delete), + onPressed: () { + _showDeletePhotosSheet(context, _photos[_selectedIndex]); + }, + ), IconButton( icon: Icon(Icons.share), onPressed: () async { - share(widget.photos[_selectedIndex]); + share(_photos[_selectedIndex]); }, ) ], ); } + + void _showDeletePhotosSheet(BuildContext context, Photo photo) { + final action = CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + child: Text("Delete on device"), + isDestructiveAction: true, + onPressed: () async { + await _deletePhoto(context, photo, false); + }, + ), + CupertinoActionSheetAction( + child: Text("Delete everywhere [WiP]"), + isDestructiveAction: true, + onPressed: () async { + await _deletePhoto(context, photo, true); + }, + ) + ], + cancelButton: CupertinoActionSheetAction( + child: Text("Cancel"), + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + ); + showCupertinoModalPopup(context: context, builder: (_) => action); + } + + Future _deletePhoto( + BuildContext context, Photo photo, bool deleteEverywhere) async { + await PhotoManager.editor.deleteWithIds([photo.localId]); + + deleteEverywhere + ? await DatabaseHelper.instance.markPhotoForDeletion(photo) + : await DatabaseHelper.instance.deletePhoto(photo); + + Navigator.of(context, rootNavigator: true).pop(); + + _pageController + .nextPage(duration: Duration(milliseconds: 250), curve: Curves.ease) + .then((value) { + if (widget.onPhotoDeleted != null) { + widget.onPhotoDeleted(photo); + } + _pageController.previousPage( + duration: Duration(milliseconds: 1), curve: Curves.linear); // h4ck + }); + + photoLoader.reloadPhotos(); + } } diff --git a/lib/ui/extents_page_view.dart b/lib/ui/extents_page_view.dart deleted file mode 100644 index 638889840..000000000 --- a/lib/ui/extents_page_view.dart +++ /dev/null @@ -1,381 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart' hide PageView; - -/// This is copy-pasted from the Flutter framework with a support added for building -/// pages off screen using [Viewport.cacheExtents] and a [LayoutBuilder] -/// -/// Based on commit 3932ffb1cd5dfa0c3891c60977ee4f9cd70ade66 on channel dev - -// Having this global (mutable) page controller is a bit of a hack. We need it -// to plumb in the factory for _PagePosition, but it will end up accumulating -// a large list of scroll positions. As long as you don't try to actually -// control the scroll positions, everything should be fine. -final PageController _defaultPageController = PageController(); -const PageScrollPhysics _kPagePhysics = PageScrollPhysics(); - -/// A scrollable list that works page by page. -/// -/// Each child of a page view is forced to be the same size as the viewport. -/// -/// You can use a [PageController] to control which page is visible in the view. -/// In addition to being able to control the pixel offset of the content inside -/// the [PageView], a [PageController] also lets you control the offset in terms -/// of pages, which are increments of the viewport size. -/// -/// The [PageController] can also be used to control the -/// [PageController.initialPage], which determines which page is shown when the -/// [PageView] is first constructed, and the [PageController.viewportFraction], -/// which determines the size of the pages as a fraction of the viewport size. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A} -/// -/// See also: -/// -/// * [PageController], which controls which page is visible in the view. -/// * [SingleChildScrollView], when you need to make a single child scrollable. -/// * [ListView], for a scrollable list of boxes. -/// * [GridView], for a scrollable grid of boxes. -/// * [ScrollNotification] and [NotificationListener], which can be used to watch -/// the scroll position without using a [ScrollController]. -class ExtentsPageView extends StatefulWidget { - /// Creates a scrollable list that works page by page from an explicit [List] - /// of widgets. - /// - /// This constructor is appropriate for page views with a small number of - /// children because constructing the [List] requires doing work for every - /// child that could possibly be displayed in the page view, instead of just - /// those children that are actually visible. - ExtentsPageView({ - Key key, - this.scrollDirection = Axis.horizontal, - this.reverse = false, - PageController controller, - this.physics, - this.pageSnapping = true, - this.onPageChanged, - List children = const [], - this.dragStartBehavior = DragStartBehavior.start, - }) : controller = controller ?? _defaultPageController, - childrenDelegate = SliverChildListDelegate(children), - extents = 0, - super(key: key); - - /// Creates a scrollable list that works page by page using widgets that are - /// created on demand. - /// - /// This constructor is appropriate for page views with a large (or infinite) - /// number of children because the builder is called only for those children - /// that are actually visible. - /// - /// Providing a non-null [itemCount] lets the [PageView] compute the maximum - /// scroll extent. - /// - /// [itemBuilder] will be called only with indices greater than or equal to - /// zero and less than [itemCount]. - /// - /// [PageView.builder] by default does not support child reordering. If - /// you are planning to change child order at a later time, consider using - /// [PageView] or [PageView.custom]. - ExtentsPageView.builder({ - Key key, - this.scrollDirection = Axis.horizontal, - this.reverse = false, - PageController controller, - this.physics, - this.pageSnapping = true, - this.onPageChanged, - @required IndexedWidgetBuilder itemBuilder, - int itemCount, - this.dragStartBehavior = DragStartBehavior.start, - }) : controller = controller ?? _defaultPageController, - childrenDelegate = - SliverChildBuilderDelegate(itemBuilder, childCount: itemCount), - extents = 0, - super(key: key); - - ExtentsPageView.extents({ - Key key, - this.extents = 1, - this.scrollDirection = Axis.horizontal, - this.reverse = false, - PageController controller, - this.physics, - this.pageSnapping = true, - this.onPageChanged, - @required IndexedWidgetBuilder itemBuilder, - int itemCount, - this.dragStartBehavior = DragStartBehavior.start, - }) : controller = controller ?? _defaultPageController, - childrenDelegate = SliverChildBuilderDelegate( - itemBuilder, - childCount: itemCount, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - ), - super(key: key); - - /// Creates a scrollable list that works page by page with a custom child - /// model. - /// - /// {@tool sample} - /// - /// This [PageView] uses a custom [SliverChildBuilderDelegate] to support child - /// reordering. - /// - /// ```dart - /// class MyPageView extends StatefulWidget { - /// @override - /// _MyPageViewState createState() => _MyPageViewState(); - /// } - /// - /// class _MyPageViewState extends State { - /// List items = ['1', '2', '3', '4', '5']; - /// - /// void _reverse() { - /// setState(() { - /// items = items.reversed.toList(); - /// }); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SafeArea( - /// child: PageView.custom( - /// childrenDelegate: SliverChildBuilderDelegate( - /// (BuildContext context, int index) { - /// return KeepAlive( - /// data: items[index], - /// key: ValueKey(items[index]), - /// ); - /// }, - /// childCount: items.length, - /// findChildIndexCallback: (Key key) { - /// final ValueKey valueKey = key; - /// final String data = valueKey.value; - /// return items.indexOf(data); - /// } - /// ), - /// ), - /// ), - /// bottomNavigationBar: BottomAppBar( - /// child: Row( - /// mainAxisAlignment: MainAxisAlignment.center, - /// children: [ - /// FlatButton( - /// onPressed: () => _reverse(), - /// child: Text('Reverse items'), - /// ), - /// ], - /// ), - /// ), - /// ); - /// } - /// } - /// - /// class KeepAlive extends StatefulWidget { - /// const KeepAlive({Key key, this.data}) : super(key: key); - /// - /// final String data; - /// - /// @override - /// _KeepAliveState createState() => _KeepAliveState(); - /// } - /// - /// class _KeepAliveState extends State with AutomaticKeepAliveClientMixin{ - /// @override - /// bool get wantKeepAlive => true; - /// - /// @override - /// Widget build(BuildContext context) { - /// super.build(context); - /// return Text(widget.data); - /// } - /// } - /// ``` - /// {@end-tool} - ExtentsPageView.custom({ - Key key, - this.scrollDirection = Axis.horizontal, - this.reverse = false, - PageController controller, - this.physics, - this.pageSnapping = true, - this.onPageChanged, - @required this.childrenDelegate, - this.dragStartBehavior = DragStartBehavior.start, - }) : assert(childrenDelegate != null), - extents = 0, - controller = controller ?? _defaultPageController, - super(key: key); - - /// The number of pages to build off screen. - /// - /// For example, a value of `1` builds one page ahead and one page behind, - /// for a total of three built pages. - /// - /// This is especially useful for making sure heavyweight widgets have a chance - /// to load off-screen before the user pulls it into the viewport. - final int extents; - - /// The axis along which the page view scrolls. - /// - /// Defaults to [Axis.horizontal]. - final Axis scrollDirection; - - /// Whether the page view scrolls in the reading direction. - /// - /// For example, if the reading direction is left-to-right and - /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from - /// left to right when [reverse] is false and from right to left when - /// [reverse] is true. - /// - /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view - /// scrolls from top to bottom when [reverse] is false and from bottom to top - /// when [reverse] is true. - /// - /// Defaults to false. - final bool reverse; - - /// An object that can be used to control the position to which this page - /// view is scrolled. - final PageController controller; - - /// How the page view should respond to user input. - /// - /// For example, determines how the page view continues to animate after the - /// user stops dragging the page view. - /// - /// The physics are modified to snap to page boundaries using - /// [PageScrollPhysics] prior to being used. - /// - /// Defaults to matching platform conventions. - final ScrollPhysics physics; - - /// Set to false to disable page snapping, useful for custom scroll behavior. - final bool pageSnapping; - - /// Called whenever the page in the center of the viewport changes. - final ValueChanged onPageChanged; - - /// A delegate that provides the children for the [PageView]. - /// - /// The [PageView.custom] constructor lets you specify this delegate - /// explicitly. The [PageView] and [PageView.builder] constructors create a - /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder], - /// respectively. - final SliverChildDelegate childrenDelegate; - - /// {@macro flutter.widgets.scrollable.dragStartBehavior} - final DragStartBehavior dragStartBehavior; - - @override - _PageViewState createState() => _PageViewState(); -} - -class _PageViewState extends State { - int _lastReportedPage = 0; - - @override - void initState() { - super.initState(); - _lastReportedPage = widget.controller.initialPage; - } - - AxisDirection _getDirection(BuildContext context) { - switch (widget.scrollDirection) { - case Axis.horizontal: - assert(debugCheckHasDirectionality(context)); - final TextDirection textDirection = Directionality.of(context); - final AxisDirection axisDirection = - textDirectionToAxisDirection(textDirection); - return widget.reverse - ? flipAxisDirection(axisDirection) - : axisDirection; - case Axis.vertical: - return widget.reverse ? AxisDirection.up : AxisDirection.down; - } - return null; - } - - @override - Widget build(BuildContext context) { - final AxisDirection axisDirection = _getDirection(context); - final ScrollPhysics physics = widget.pageSnapping - ? _kPagePhysics.applyTo(widget.physics) - : widget.physics; - - return NotificationListener( - onNotification: (ScrollNotification notification) { - if (notification.depth == 0 && - widget.onPageChanged != null && - notification is ScrollUpdateNotification) { - final PageMetrics metrics = notification.metrics; - final int currentPage = metrics.page.round(); - if (currentPage != _lastReportedPage) { - _lastReportedPage = currentPage; - widget.onPageChanged(currentPage); - } - } - return false; - }, - child: Scrollable( - dragStartBehavior: widget.dragStartBehavior, - axisDirection: axisDirection, - controller: widget.controller, - physics: physics, - viewportBuilder: (BuildContext context, ViewportOffset position) { - return LayoutBuilder( - builder: (context, constraints) { - assert(constraints.hasBoundedHeight); - assert(constraints.hasBoundedWidth); - - double cacheExtent; - - switch (widget.scrollDirection) { - case Axis.vertical: - cacheExtent = constraints.maxHeight * widget.extents; - break; - - case Axis.horizontal: - default: - cacheExtent = constraints.maxWidth * widget.extents; - break; - } - - return Viewport( - cacheExtent: cacheExtent, - axisDirection: axisDirection, - offset: position, - slivers: [ - SliverFillViewport( - viewportFraction: widget.controller.viewportFraction, - delegate: widget.childrenDelegate, - ), - ], - ); - }, - ); - }, - ), - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder description) { - super.debugFillProperties(description); - description - .add(EnumProperty('scrollDirection', widget.scrollDirection)); - description.add( - FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed')); - description.add(DiagnosticsProperty( - 'controller', widget.controller, - showName: false)); - description.add(DiagnosticsProperty( - 'physics', widget.physics, - showName: false)); - description.add(FlagProperty('pageSnapping', - value: widget.pageSnapping, ifFalse: 'snapping disabled')); - } -} diff --git a/lib/ui/gallery.dart b/lib/ui/gallery.dart index 997f57b90..30aed1597 100644 --- a/lib/ui/gallery.dart +++ b/lib/ui/gallery.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:logger/logger.dart'; import 'package:myapp/core/thumbnail_cache.dart'; import 'package:myapp/models/photo.dart'; import 'package:myapp/photo_loader.dart'; @@ -31,6 +32,13 @@ class _GalleryState extends State { final Set _selectedPhotos = HashSet(); PhotoLoader get photoLoader => Provider.of(context); bool _shouldSelectOnTap = false; + List _photos; + + @override + void initState() { + _photos = widget.photos; + super.initState(); + } @override Widget build(BuildContext context) { @@ -122,7 +130,16 @@ class _GalleryState extends State { } void routeToDetailPage(Photo photo, BuildContext context) { - final page = DetailPage(widget.photos, widget.photos.indexOf(photo)); + final page = DetailPage( + _photos, + _photos.indexOf(photo), + onPhotoDeleted: (photo) { + setState(() { + Logger().i("Photo deleted! "); + _photos.remove(photo); + }); + }, + ); Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { @@ -135,16 +152,15 @@ class _GalleryState extends State { void _collatePhotos() { final dailyPhotos = List(); final collatedPhotos = List>(); - for (int index = 0; index < widget.photos.length; index++) { + for (int index = 0; index < _photos.length; index++) { if (index > 0 && - !_arePhotosFromSameDay( - widget.photos[index], widget.photos[index - 1])) { + !_arePhotosFromSameDay(_photos[index], _photos[index - 1])) { var collatedDailyPhotos = List(); collatedDailyPhotos.addAll(dailyPhotos); collatedPhotos.add(collatedDailyPhotos); dailyPhotos.clear(); } - dailyPhotos.add(widget.photos[index]); + dailyPhotos.add(_photos[index]); } if (dailyPhotos.isNotEmpty) { collatedPhotos.add(dailyPhotos); diff --git a/lib/ui/zoomable_image.dart b/lib/ui/zoomable_image.dart index 09f724733..8d154c227 100644 --- a/lib/ui/zoomable_image.dart +++ b/lib/ui/zoomable_image.dart @@ -46,8 +46,6 @@ class _ZoomableImageState extends State { if (cachedThumbnail != null) { _imageProvider = Image.memory(cachedThumbnail).image; _loadedThumbnail = true; - } else { - Logger().i("Thumbnail missing for " + widget.photo.toString()); } }