diff --git a/lib/ui/gallery.dart b/lib/ui/gallery.dart index 1a4f35c50..243ca8d60 100644 --- a/lib/ui/gallery.dart +++ b/lib/ui/gallery.dart @@ -1,34 +1,36 @@ -import 'dart:io'; +import 'dart:collection'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; -import 'package:myapp/db/db_helper.dart'; import 'package:myapp/models/photo.dart'; -import 'package:path/path.dart' as path; import 'package:myapp/photo_loader.dart'; import 'package:myapp/ui/image_widget.dart'; import 'package:myapp/utils/date_time_util.dart'; import 'package:provider/provider.dart'; -import 'package:share_extend/share_extend.dart'; import 'detail_page.dart'; class Gallery extends StatefulWidget { final List photos; + _GalleryState _state; + Function(Set) photoSelectionChangeCallback; - Gallery(this.photos); + Gallery(this.photos, {this.photoSelectionChangeCallback}); @override _GalleryState createState() { - return _GalleryState(); + _state = _GalleryState(); + return _state; } } class _GalleryState extends State { - PhotoLoader get photoLoader => Provider.of(context); final ScrollController _scrollController = ScrollController(); final List> _collatedPhotos = List>(); + final Set _selectedPhotos = HashSet(); + PhotoLoader get photoLoader => Provider.of(context); + bool _shouldSelectOnTap = false; @override Widget build(BuildContext context) { @@ -80,87 +82,41 @@ class _GalleryState extends State { Widget _buildPhoto(BuildContext context, Photo photo) { return GestureDetector( onTap: () { - routeToDetailPage(photo, context); + if (_shouldSelectOnTap) { + _selectPhoto(photo); + } else { + routeToDetailPage(photo, context); + } }, onLongPress: () { - _showPopup(photo, context); + _selectPhoto(photo); }, - child: Padding( - padding: const EdgeInsets.all(2.0), + child: Container( + margin: const EdgeInsets.all(2.0), + decoration: BoxDecoration( + border: _selectedPhotos.contains(photo) + ? Border.all(width: 4.0, color: Colors.blue) + : null, + ), child: ImageWidget(photo), ), ); } - void _showPopup(Photo photo, BuildContext context) { - final action = CupertinoActionSheet( - title: Text(path.basename(photo.localPath)), - actions: [ - CupertinoActionSheetAction( - child: Text("Share"), - isDefaultAction: true, - onPressed: () { - ShareExtend.share(photo.localPath, "image"); - Navigator.pop(context); - }, - ), - CupertinoActionSheetAction( - child: Text("Delete"), - isDestructiveAction: true, - onPressed: () { - Navigator.pop(context); - _showDeletePopup(photo, context); - }, - ) - ], - cancelButton: CupertinoActionSheetAction( - child: Text("Cancel"), - onPressed: () { - Navigator.pop(context); - }, - ), - ); - showCupertinoModalPopup(context: context, builder: (_) => action); - } - - void _showDeletePopup(Photo photo, BuildContext context) { - final action = CupertinoActionSheet( - actions: [ - 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) { diff --git a/lib/ui/gallery_container_widget.dart b/lib/ui/gallery_container_widget.dart index 74a31b851..87e3f3eac 100644 --- a/lib/ui/gallery_container_widget.dart +++ b/lib/ui/gallery_container_widget.dart @@ -12,8 +12,10 @@ import '../photo_loader.dart'; import 'gallery.dart'; import 'loading_widget.dart'; -class GalleryContainer extends StatelessWidget { +// TODO: Remove redundant layer +class GalleryContainer extends StatefulWidget { final GalleryType type; + final Function(Set) photoSelectionChangeCallback; static final importantItemsFilter = ImportantItemsFilter(); static final galleryItemsFilter = GalleryItemsFilter(); @@ -21,11 +23,18 @@ class GalleryContainer extends StatelessWidget { const GalleryContainer( this.type, { Key key, + this.photoSelectionChangeCallback, }) : super(key: key); + @override + _GalleryContainerState createState() => _GalleryContainerState(); +} + +class _GalleryContainerState extends State { + PhotoLoader get photoLoader => Provider.of(context); + @override Widget build(BuildContext context) { - final photoLoader = PhotoLoader.instance; return Column( children: [ Hero( @@ -51,14 +60,11 @@ class GalleryContainer extends StatelessWidget { future: photoLoader.loadPhotos(), builder: (context, snapshot) { if (snapshot.hasData) { - return ChangeNotifierProvider.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) { return Text("Error!"); } else { @@ -71,9 +77,15 @@ class GalleryContainer extends StatelessWidget { } Gallery _getGallery(List 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 getFilteredPhotos( diff --git a/lib/ui/home_widget.dart b/lib/ui/home_widget.dart index e448fc33b..82b368167 100644 --- a/lib/ui/home_widget.dart +++ b/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/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'; @@ -13,7 +22,9 @@ class HomeWidget extends StatefulWidget { } class _HomeWidgetState extends State { - int _selectedIndex = 0; + PhotoLoader get photoLoader => Provider.of(context); + int _selectedNavBarItem = 0; + Set _selectedPhotos = Set(); @override Widget build(BuildContext context) { @@ -21,34 +32,133 @@ class _HomeWidgetState extends State { title: widget.title, theme: ThemeData.dark(), 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 selectedPhotos) { + setState(() { + _selectedPhotos = selectedPhotos; + }); + }, ), - bottomNavigationBar: BottomNavigationBar( - items: const [ - 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, - ), - body: GalleryContainer(_selectedIndex == 0 - ? GalleryType.important_photos - : GalleryType.all_photos), ), ); } - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); + BottomNavigationBar _buildBottomNavigationBar() { + return BottomNavigationBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.photo_filter), + title: Text('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), + ); + } + + List _getActions(BuildContext context) { + List actions = List(); + 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(); + for (Photo photo in _selectedPhotos) { + photoPaths.add(photo.localPath); + } + ShareExtend.shareMultiple(photoPaths, "image"); + } + + void _showDeletePhotosSheet(BuildContext context) { + final action = CupertinoActionSheet( + actions: [ + 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); } }