Add multi select for sharing and deleting

This commit is contained in:
Vishnu Mohandas 2020-04-18 02:12:38 +05:30
parent 4680c230bc
commit ad3ea98c14
3 changed files with 196 additions and 118 deletions

View file

@ -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<Photo> photos;
_GalleryState _state;
Function(Set<Photo>) photoSelectionChangeCallback;
Gallery(this.photos);
Gallery(this.photos, {this.photoSelectionChangeCallback});
@override
_GalleryState createState() {
return _GalleryState();
_state = _GalleryState();
return _state;
}
}
class _GalleryState extends State<Gallery> {
PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
final ScrollController _scrollController = ScrollController();
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
Widget build(BuildContext context) {
@ -80,87 +82,41 @@ class _GalleryState extends State<Gallery> {
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: <Widget>[
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: <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) {

View file

@ -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<Photo>) 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<GalleryContainer> {
PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
@override
Widget build(BuildContext context) {
final photoLoader = PhotoLoader.instance;
return Column(
children: <Widget>[
Hero(
@ -51,14 +60,11 @@ class GalleryContainer extends StatelessWidget {
future: photoLoader.loadPhotos(),
builder: (context, snapshot) {
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) {
return Text("Error!");
} else {
@ -71,9 +77,15 @@ class GalleryContainer extends StatelessWidget {
}
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(

View file

@ -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<HomeWidget> {
int _selectedIndex = 0;
PhotoLoader get photoLoader => Provider.of<PhotoLoader>(context);
int _selectedNavBarItem = 0;
Set<Photo> _selectedPhotos = Set<Photo>();
@override
Widget build(BuildContext context) {
@ -21,34 +32,133 @@ class _HomeWidgetState extends State<HomeWidget> {
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<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,
),
body: GalleryContainer(_selectedIndex == 0
? GalleryType.important_photos
: GalleryType.all_photos),
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
BottomNavigationBar _buildBottomNavigationBar() {
return BottomNavigationBar(
items: const <BottomNavigationBarItem>[
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<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);
}
}