Forráskód Böngészése

Add delete button to detail screen

Vishnu Mohandas 5 éve
szülő
commit
e800f27a60

+ 0 - 1
lib/ui/album_widget.dart

@@ -19,7 +19,6 @@ class _AlbumPageState extends State<AlbumPage> {
 
   @override
   Widget build(Object context) {
-    Logger().i("Building with " + widget.album.photos.length.toString());
     return Scaffold(
       appBar: GalleryAppBarWidget(
         widget.album.name,

+ 115 - 32
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<Photo> 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<DetailPage> {
+  PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
   bool _shouldDisableScroll = false;
+  List<Photo> _photos;
   int _selectedIndex = 0;
-  final _cachedImages = LRUMap<int, ZoomableImage>(5);
+  PageController _pageController;
+  LRUMap<int, ZoomableImage> _cachedImages;
 
   @override
-  Widget build(BuildContext context) {
+  void initState() {
+    Logger().i("initState");
+    _photos = widget.photos;
     _selectedIndex = widget.selectedIndex;
+    _cachedImages = LRUMap<int, ZoomableImage>(5);
+    super.initState();
+  }
 
-    Logger().i("Loading " + widget.photos[_selectedIndex].toString());
-    var pageController = PageController(initialPage: _selectedIndex);
+  @override
+  Widget build(BuildContext context) {
+    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: <Widget>[
+        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: <Widget>[
+        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();
+  }
 }

+ 0 - 381
lib/ui/extents_page_view.dart

@@ -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<Widget> children = const <Widget>[],
-    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<MyPageView> {
-  ///   List<String> items = <String>['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<String>(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: <Widget>[
-  ///             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<KeepAlive> 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<int> 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<ExtentsPageView> {
-  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<ScrollNotification>(
-      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: <Widget>[
-                  SliverFillViewport(
-                    viewportFraction: widget.controller.viewportFraction,
-                    delegate: widget.childrenDelegate,
-                  ),
-                ],
-              );
-            },
-          );
-        },
-      ),
-    );
-  }
-
-  @override
-  void debugFillProperties(DiagnosticPropertiesBuilder description) {
-    super.debugFillProperties(description);
-    description
-        .add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
-    description.add(
-        FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'));
-    description.add(DiagnosticsProperty<PageController>(
-        'controller', widget.controller,
-        showName: false));
-    description.add(DiagnosticsProperty<ScrollPhysics>(
-        'physics', widget.physics,
-        showName: false));
-    description.add(FlagProperty('pageSnapping',
-        value: widget.pageSnapping, ifFalse: 'snapping disabled'));
-  }
-}

+ 21 - 5
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<Gallery> {
   final Set<Photo> _selectedPhotos = HashSet<Photo>();
   PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
   bool _shouldSelectOnTap = false;
+  List<Photo> _photos;
+
+  @override
+  void initState() {
+    _photos = widget.photos;
+    super.initState();
+  }
 
   @override
   Widget build(BuildContext context) {
@@ -122,7 +130,16 @@ class _GalleryState extends State<Gallery> {
   }
 
   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<Gallery> {
   void _collatePhotos() {
     final dailyPhotos = List<Photo>();
     final collatedPhotos = List<List<Photo>>();
-    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<Photo>();
         collatedDailyPhotos.addAll(dailyPhotos);
         collatedPhotos.add(collatedDailyPhotos);
         dailyPhotos.clear();
       }
-      dailyPhotos.add(widget.photos[index]);
+      dailyPhotos.add(_photos[index]);
     }
     if (dailyPhotos.isNotEmpty) {
       collatedPhotos.add(dailyPhotos);

+ 0 - 2
lib/ui/zoomable_image.dart

@@ -46,8 +46,6 @@ class _ZoomableImageState extends State<ZoomableImage> {
       if (cachedThumbnail != null) {
         _imageProvider = Image.memory(cachedThumbnail).image;
         _loadedThumbnail = true;
-      } else {
-        Logger().i("Thumbnail missing for " + widget.photo.toString());
       }
     }