Browse Source

Add multi select for sharing and deleting

Vishnu Mohandas 5 years ago
parent
commit
ad3ea98c14
3 changed files with 195 additions and 117 deletions
  1. 36 80
      lib/ui/gallery.dart
  2. 25 13
      lib/ui/gallery_container_widget.dart
  3. 134 24
      lib/ui/home_widget.dart

+ 36 - 80
lib/ui/gallery.dart

@@ -1,34 +1,36 @@
-import 'dart:io';
+import 'dart:collection';
 
 
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:logger/logger.dart';
 import 'package:logger/logger.dart';
-import 'package:myapp/db/db_helper.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/models/photo.dart';
-import 'package:path/path.dart' as path;
 import 'package:myapp/photo_loader.dart';
 import 'package:myapp/photo_loader.dart';
 import 'package:myapp/ui/image_widget.dart';
 import 'package:myapp/ui/image_widget.dart';
 import 'package:myapp/utils/date_time_util.dart';
 import 'package:myapp/utils/date_time_util.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
-import 'package:share_extend/share_extend.dart';
 
 
 import 'detail_page.dart';
 import 'detail_page.dart';
 
 
 class Gallery extends StatefulWidget {
 class Gallery extends StatefulWidget {
   final List<Photo> photos;
   final List<Photo> photos;
+  _GalleryState _state;
+  Function(Set<Photo>) photoSelectionChangeCallback;
 
 
-  Gallery(this.photos);
+  Gallery(this.photos, {this.photoSelectionChangeCallback});
 
 
   @override
   @override
   _GalleryState createState() {
   _GalleryState createState() {
-    return _GalleryState();
+    _state = _GalleryState();
+    return _state;
   }
   }
 }
 }
 
 
 class _GalleryState extends State<Gallery> {
 class _GalleryState extends State<Gallery> {
-  PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
   final ScrollController _scrollController = ScrollController();
   final ScrollController _scrollController = ScrollController();
   final List<List<Photo>> _collatedPhotos = List<List<Photo>>();
   final List<List<Photo>> _collatedPhotos = List<List<Photo>>();
+  final Set<Photo> _selectedPhotos = HashSet<Photo>();
+  PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
+  bool _shouldSelectOnTap = false;
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -80,87 +82,41 @@ class _GalleryState extends State<Gallery> {
   Widget _buildPhoto(BuildContext context, Photo photo) {
   Widget _buildPhoto(BuildContext context, Photo photo) {
     return GestureDetector(
     return GestureDetector(
       onTap: () {
       onTap: () {
-        routeToDetailPage(photo, context);
+        if (_shouldSelectOnTap) {
+          _selectPhoto(photo);
+        } else {
+          routeToDetailPage(photo, context);
+        }
       },
       },
       onLongPress: () {
       onLongPress: () {
-        _showPopup(photo, context);
+        _selectPhoto(photo);
       },
       },
-      child: Padding(
-        padding: const EdgeInsets.all(2.0),
-        child: ImageWidget(photo),
-      ),
-    );
-  }
-
-  void _showPopup(Photo photo, BuildContext context) {
-    final action = CupertinoActionSheet(
-      title: Text(path.basename(photo.localPath)),
-      actions: <Widget>[
-        CupertinoActionSheetAction(
-          child: Text("Share"),
-          isDefaultAction: true,
-          onPressed: () {
-            ShareExtend.share(photo.localPath, "image");
-            Navigator.pop(context);
-          },
+      child: Container(
+        margin: const EdgeInsets.all(2.0),
+        decoration: BoxDecoration(
+          border: _selectedPhotos.contains(photo)
+              ? Border.all(width: 4.0, color: Colors.blue)
+              : null,
         ),
         ),
-        CupertinoActionSheetAction(
-          child: Text("Delete"),
-          isDestructiveAction: true,
-          onPressed: () {
-            Navigator.pop(context);
-            _showDeletePopup(photo, context);
-          },
-        )
-      ],
-      cancelButton: CupertinoActionSheetAction(
-        child: Text("Cancel"),
-        onPressed: () {
-          Navigator.pop(context);
-        },
+        child: ImageWidget(photo),
       ),
       ),
     );
     );
-    showCupertinoModalPopup(context: context, builder: (_) => action);
   }
   }
 
 
-  void _showDeletePopup(Photo photo, BuildContext context) {
-    final action = CupertinoActionSheet(
-      actions: <Widget>[
-        CupertinoActionSheetAction(
-          child: Text("Delete on device"),
-          isDestructiveAction: true,
-          onPressed: () {
-            DatabaseHelper.instance.deletePhoto(photo).then((_) {
-              File file = File(photo.localPath);
-              file.delete().then((_) {
-                photoLoader.reloadPhotos();
-                Navigator.pop(context);
-              });
-            });
-          },
-        ),
-        CupertinoActionSheetAction(
-          child: Text("Delete everywhere [WiP]"),
-          isDestructiveAction: true,
-          onPressed: () {
-            DatabaseHelper.instance.markPhotoAsDeleted(photo).then((_) {
-              File file = File(photo.localPath);
-              file.delete().then((_) {
-                photoLoader.reloadPhotos();
-                Navigator.pop(context);
-              });
-            });
-          },
-        )
-      ],
-      cancelButton: CupertinoActionSheetAction(
-        child: Text("Cancel"),
-        onPressed: () {
-          Navigator.pop(context);
-        },
-      ),
-    );
-    showCupertinoModalPopup(context: context, builder: (_) => action);
+  void _selectPhoto(Photo photo) {
+    setState(() {
+      if (_selectedPhotos.contains(photo)) {
+        _selectedPhotos.remove(photo);
+      } else {
+        _selectedPhotos.add(photo);
+      }
+      if (_selectedPhotos.isNotEmpty) {
+        _shouldSelectOnTap = true;
+      } else {
+        _shouldSelectOnTap = false;
+      }
+      widget.photoSelectionChangeCallback(_selectedPhotos);
+    });
   }
   }
 
 
   void routeToDetailPage(Photo photo, BuildContext context) {
   void routeToDetailPage(Photo photo, BuildContext context) {

+ 25 - 13
lib/ui/gallery_container_widget.dart

@@ -12,8 +12,10 @@ import '../photo_loader.dart';
 import 'gallery.dart';
 import 'gallery.dart';
 import 'loading_widget.dart';
 import 'loading_widget.dart';
 
 
-class GalleryContainer extends StatelessWidget {
+// TODO: Remove redundant layer
+class GalleryContainer extends StatefulWidget {
   final GalleryType type;
   final GalleryType type;
+  final Function(Set<Photo>) photoSelectionChangeCallback;
 
 
   static final importantItemsFilter = ImportantItemsFilter();
   static final importantItemsFilter = ImportantItemsFilter();
   static final galleryItemsFilter = GalleryItemsFilter();
   static final galleryItemsFilter = GalleryItemsFilter();
@@ -21,11 +23,18 @@ class GalleryContainer extends StatelessWidget {
   const GalleryContainer(
   const GalleryContainer(
     this.type, {
     this.type, {
     Key key,
     Key key,
+    this.photoSelectionChangeCallback,
   }) : super(key: key);
   }) : super(key: key);
 
 
+  @override
+  _GalleryContainerState createState() => _GalleryContainerState();
+}
+
+class _GalleryContainerState extends State<GalleryContainer> {
+  PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    final photoLoader = PhotoLoader.instance;
     return Column(
     return Column(
       children: <Widget>[
       children: <Widget>[
         Hero(
         Hero(
@@ -51,14 +60,11 @@ class GalleryContainer extends StatelessWidget {
           future: photoLoader.loadPhotos(),
           future: photoLoader.loadPhotos(),
           builder: (context, snapshot) {
           builder: (context, snapshot) {
             if (snapshot.hasData) {
             if (snapshot.hasData) {
-              return ChangeNotifierProvider<PhotoLoader>.value(
-                value: photoLoader,
-                child: ChangeNotifierBuilder(
-                    value: photoLoader,
-                    builder: (_, __) {
-                      return Flexible(child: _getGallery(photoLoader.photos));
-                    }),
-              );
+              return ChangeNotifierBuilder(
+                  value: photoLoader,
+                  builder: (_, __) {
+                    return Flexible(child: _getGallery(photoLoader.photos));
+                  });
             } else if (snapshot.hasError) {
             } else if (snapshot.hasError) {
               return Text("Error!");
               return Text("Error!");
             } else {
             } else {
@@ -71,9 +77,15 @@ class GalleryContainer extends StatelessWidget {
   }
   }
 
 
   Gallery _getGallery(List<Photo> photos) {
   Gallery _getGallery(List<Photo> photos) {
-    return type == GalleryType.important_photos
-        ? Gallery(getFilteredPhotos(photos, importantItemsFilter))
-        : Gallery(getFilteredPhotos(photos, galleryItemsFilter));
+    return widget.type == GalleryType.important_photos
+        ? Gallery(
+            getFilteredPhotos(photos, GalleryContainer.importantItemsFilter),
+            photoSelectionChangeCallback: widget.photoSelectionChangeCallback,
+          )
+        : Gallery(
+            getFilteredPhotos(photos, GalleryContainer.galleryItemsFilter),
+            photoSelectionChangeCallback: widget.photoSelectionChangeCallback,
+          );
   }
   }
 
 
   List<Photo> getFilteredPhotos(
   List<Photo> getFilteredPhotos(

+ 134 - 24
lib/ui/home_widget.dart

@@ -1,5 +1,14 @@
+import 'dart:io';
+
+import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
+import 'package:logger/logger.dart';
+import 'package:myapp/db/db_helper.dart';
+import 'package:myapp/models/photo.dart';
+import 'package:myapp/photo_loader.dart';
+import 'package:provider/provider.dart';
+import 'package:share_extend/share_extend.dart';
 
 
 import 'gallery_container_widget.dart';
 import 'gallery_container_widget.dart';
 
 
@@ -13,7 +22,9 @@ class HomeWidget extends StatefulWidget {
 }
 }
 
 
 class _HomeWidgetState extends State<HomeWidget> {
 class _HomeWidgetState extends State<HomeWidget> {
-  int _selectedIndex = 0;
+  PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
+  int _selectedNavBarItem = 0;
+  Set<Photo> _selectedPhotos = Set<Photo>();
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -21,34 +32,133 @@ class _HomeWidgetState extends State<HomeWidget> {
       title: widget.title,
       title: widget.title,
       theme: ThemeData.dark(),
       theme: ThemeData.dark(),
       home: Scaffold(
       home: Scaffold(
-        appBar: AppBar(
-          title: Text(widget.title),
+        appBar: _buildAppBar(context),
+        bottomNavigationBar: _buildBottomNavigationBar(),
+        body: GalleryContainer(
+          _selectedNavBarItem == 0
+              ? GalleryType.important_photos
+              : GalleryType.all_photos,
+          photoSelectionChangeCallback: (Set<Photo> selectedPhotos) {
+            setState(() {
+              _selectedPhotos = selectedPhotos;
+            });
+          },
         ),
         ),
-        bottomNavigationBar: BottomNavigationBar(
-          items: const <BottomNavigationBarItem>[
-            BottomNavigationBarItem(
-              icon: Icon(Icons.photo_filter),
-              title: Text('Photos'),
-            ),
-            BottomNavigationBarItem(
-              icon: Icon(Icons.photo_library),
-              title: Text('Gallery'),
-            ),
-          ],
-          currentIndex: _selectedIndex,
-          selectedItemColor: Colors.yellow[800],
-          onTap: _onItemTapped,
+      ),
+    );
+  }
+
+  BottomNavigationBar _buildBottomNavigationBar() {
+    return BottomNavigationBar(
+      items: const <BottomNavigationBarItem>[
+        BottomNavigationBarItem(
+          icon: Icon(Icons.photo_filter),
+          title: Text('Photos'),
         ),
         ),
-        body: GalleryContainer(_selectedIndex == 0
-            ? GalleryType.important_photos
-            : GalleryType.all_photos),
+        BottomNavigationBarItem(
+          icon: Icon(Icons.photo_library),
+          title: Text('Gallery'),
+        ),
+      ],
+      currentIndex: _selectedNavBarItem,
+      selectedItemColor: Colors.yellow[800],
+      onTap: (index) {
+        setState(() {
+          _selectedNavBarItem = index;
+        });
+      },
+    );
+  }
+
+  Widget _buildAppBar(BuildContext context) {
+    if (_selectedPhotos.isEmpty) {
+      return AppBar(title: Text(widget.title));
+    }
+
+    return AppBar(
+      leading: IconButton(
+        icon: Icon(Icons.close),
+        onPressed: () {
+          setState(() {
+            _selectedPhotos.clear();
+          });
+        },
       ),
       ),
+      title: Text(_selectedPhotos.length.toString()),
+      actions: _getActions(context),
     );
     );
   }
   }
 
 
-  void _onItemTapped(int index) {
-    setState(() {
-      _selectedIndex = index;
-    });
+  List<Widget> _getActions(BuildContext context) {
+    List<Widget> actions = List<Widget>();
+    if (_selectedPhotos.isNotEmpty) {
+      actions.add(IconButton(
+        icon: Icon(Icons.delete),
+        onPressed: () {
+          _showDeletePhotosSheet(context);
+        },
+      ));
+      actions.add(IconButton(
+        icon: Icon(Icons.share),
+        onPressed: () {
+          _shareSelectedPhotos(context);
+        },
+      ));
+    }
+    return actions;
+  }
+
+  void _shareSelectedPhotos(BuildContext context) {
+    var photoPaths = List<String>();
+    for (Photo photo in _selectedPhotos) {
+      photoPaths.add(photo.localPath);
+    }
+    ShareExtend.shareMultiple(photoPaths, "image");
+  }
+
+  void _showDeletePhotosSheet(BuildContext context) {
+    final action = CupertinoActionSheet(
+      actions: <Widget>[
+        CupertinoActionSheetAction(
+          child: Text("Delete on device"),
+          isDestructiveAction: true,
+          onPressed: () async {
+            for (Photo photo in _selectedPhotos) {
+              await DatabaseHelper.instance.deletePhoto(photo);
+              File file = File(photo.localPath);
+              await file.delete();
+            }
+            photoLoader.reloadPhotos();
+            setState(() {
+              _selectedPhotos.clear();
+            });
+            Navigator.pop(context);
+          },
+        ),
+        CupertinoActionSheetAction(
+          child: Text("Delete everywhere [WiP]"),
+          isDestructiveAction: true,
+          onPressed: () async {
+            for (Photo photo in _selectedPhotos) {
+              await DatabaseHelper.instance.markPhotoAsDeleted(photo);
+              File file = File(photo.localPath);
+              await file.delete();
+            }
+            photoLoader.reloadPhotos();
+            setState(() {
+              _selectedPhotos.clear();
+            });
+            Navigator.pop(context);
+          },
+        )
+      ],
+      cancelButton: CupertinoActionSheetAction(
+        child: Text("Cancel"),
+        onPressed: () {
+          Navigator.pop(context);
+        },
+      ),
+    );
+    showCupertinoModalPopup(context: context, builder: (_) => action);
   }
   }
 }
 }