Add multi select for sharing and deleting
This commit is contained in:
parent
4680c230bc
commit
ad3ea98c14
3 changed files with 196 additions and 118 deletions
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue