Simplify the state management for selected files

This commit is contained in:
Vishnu Mohandas 2020-07-16 01:59:29 +05:30
parent 605b9d5da6
commit 02f7e801c6
8 changed files with 69 additions and 62 deletions

View file

@ -0,0 +1,22 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:photos/models/file.dart';
class SelectedFiles extends ChangeNotifier {
final files = Set<File>();
void toggleSelection(File file) {
if (files.contains(file)) {
files.remove(file);
} else {
files.add(file);
}
notifyListeners();
}
void clearAll() {
files.clear();
notifyListeners();
}
}

View file

@ -4,9 +4,9 @@ import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/device_folder.dart';
import 'package:photos/models/file.dart';
import 'package:photos/file_repository.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/gallery.dart';
import 'package:photos/ui/gallery_app_bar_widget.dart';
import 'package:logging/logging.dart';
class DeviceFolderPage extends StatefulWidget {
final DeviceFolder folder;
@ -18,7 +18,7 @@ class DeviceFolderPage extends StatefulWidget {
}
class _DeviceFolderPageState extends State<DeviceFolderPage> {
final logger = Logger("DeviceFolderPageState");
final _selectedFiles = SelectedFiles();
@override
Widget build(Object context) {
@ -26,12 +26,13 @@ class _DeviceFolderPageState extends State<DeviceFolderPage> {
syncLoader: () => _getFilteredFiles(FileRepository.instance.files),
reloadEvent: Bus.instance.on<LocalPhotosUpdatedEvent>(),
tagPrefix: "device_folder",
selectedFiles: _selectedFiles,
);
return Scaffold(
appBar: GalleryAppBarWidget(
gallery,
GalleryAppBarType.local_folder,
widget.folder.name,
_selectedFiles,
widget.folder.thumbnail.deviceFolder,
),
body: gallery,

View file

@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:photos/face_search_manager.dart';
import 'package:photos/models/face.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/gallery.dart';
import 'package:photos/ui/gallery_app_bar_widget.dart';
class FaceSearchResultsPage extends StatelessWidget {
final FaceSearchManager _faceSearchManager = FaceSearchManager.instance;
final Face face;
final selectedFiles = SelectedFiles();
FaceSearchResultsPage(this.face, {Key key}) : super(key: key);
@ -16,12 +18,13 @@ class FaceSearchResultsPage extends StatelessWidget {
asyncLoader: (offset, limit) =>
_faceSearchManager.getFaceSearchResults(face, offset, limit),
tagPrefix: "face_search_results",
selectedFiles: selectedFiles,
);
return Scaffold(
appBar: GalleryAppBarWidget(
gallery,
GalleryAppBarType.search_results,
"Search results",
selectedFiles,
),
body: Container(
child: gallery,

View file

@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:photos/events/event.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/detail_page.dart';
import 'package:photos/ui/loading_widget.dart';
import 'package:photos/ui/sync_indicator.dart';
@ -21,7 +22,7 @@ class Gallery extends StatefulWidget {
// should have done the job.
final Stream<Event> reloadEvent;
final Future<void> Function() onRefresh;
final fileSelectionChangeListeners = new List<Function(Set<File>)>();
final SelectedFiles selectedFiles;
final String tagPrefix;
Gallery({
@ -29,19 +30,15 @@ class Gallery extends StatefulWidget {
this.asyncLoader,
this.reloadEvent,
this.onRefresh,
this.tagPrefix,
@required this.selectedFiles,
@required this.tagPrefix,
});
_GalleryState state;
@override
_GalleryState createState() {
state = _GalleryState();
return state;
}
void clearSelection() {
state.clearSelection();
return _GalleryState();
}
}
@ -56,7 +53,6 @@ class _GalleryState extends State<Gallery> {
bool _requiresLoad = false;
bool _hasLoadedAll = false;
double _scrollOffset = 0;
Set<File> _selectedFiles = Set<File>();
List<File> _files;
RefreshController _refreshController = RefreshController();
@ -70,13 +66,10 @@ class _GalleryState extends State<Gallery> {
});
});
}
super.initState();
}
void clearSelection() {
setState(() {
_selectedFiles = Set<File>();
widget.selectedFiles.addListener(() {
setState(() {});
});
super.initState();
}
@override
@ -204,7 +197,7 @@ class _GalleryState extends State<Gallery> {
Widget _buildFile(BuildContext context, File file) {
return GestureDetector(
onTap: () {
if (_selectedFiles.isNotEmpty) {
if (widget.selectedFiles.files.isNotEmpty) {
_selectFile(file);
} else {
_routeToDetailPage(file, context);
@ -217,7 +210,7 @@ class _GalleryState extends State<Gallery> {
child: Container(
margin: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
border: _selectedFiles.contains(file)
border: widget.selectedFiles.files.contains(file)
? Border.all(width: 4.0, color: Colors.blue)
: null,
),
@ -230,16 +223,7 @@ class _GalleryState extends State<Gallery> {
}
void _selectFile(File file) {
setState(() {
if (_selectedFiles.contains(file)) {
_selectedFiles.remove(file);
} else {
_selectedFiles.add(file);
}
for (final listener in widget.fileSelectionChangeListeners) {
listener.call(_selectedFiles);
}
});
widget.selectedFiles.toggleSelection(file);
}
void _routeToDetailPage(File file, BuildContext context) {

View file

@ -7,7 +7,7 @@ import 'package:photos/db/file_db.dart';
import 'package:photos/events/remote_sync_event.dart';
import 'package:photos/models/file.dart';
import 'package:photos/file_repository.dart';
import 'package:photos/ui/gallery.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/setup_page.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:photos/ui/share_folder_widget.dart';
@ -23,15 +23,15 @@ enum GalleryAppBarType {
class GalleryAppBarWidget extends StatefulWidget
implements PreferredSizeWidget {
final Gallery gallery;
final GalleryAppBarType type;
final String title;
final SelectedFiles selectedFiles;
final String path;
GalleryAppBarWidget(
this.gallery,
this.type,
this.title, [
this.title,
this.selectedFiles, [
this.path,
]);
@ -45,7 +45,6 @@ class GalleryAppBarWidget extends StatefulWidget
class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
bool _hasSyncErrors = false;
StreamSubscription<RemoteSyncEvent> _subscription;
Set<File> _selectedFiles = Set<File>();
@override
void initState() {
@ -54,18 +53,15 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
_hasSyncErrors = !event.success;
});
});
if (widget.gallery != null)
widget.gallery.fileSelectionChangeListeners.add((selectedFiles) {
setState(() {
_selectedFiles = selectedFiles;
});
});
widget.selectedFiles.addListener(() {
setState(() {});
});
super.initState();
}
@override
Widget build(BuildContext context) {
if (_selectedFiles.isEmpty) {
if (widget.selectedFiles.files.isEmpty) {
return AppBar(
title: Text(widget.title),
actions: _getDefaultActions(context),
@ -79,7 +75,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
_clearSelectedFiles();
},
),
title: Text(_selectedFiles.length.toString()),
title: Text(widget.selectedFiles.files.length.toString()),
actions: _getActions(context),
);
}
@ -118,7 +114,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
List<Widget> _getActions(BuildContext context) {
List<Widget> actions = List<Widget>();
if (_selectedFiles.isNotEmpty) {
if (widget.selectedFiles.files.isNotEmpty) {
if (widget.type != GalleryAppBarType.remote_folder &&
widget.type != GalleryAppBarType.search_results) {
actions.add(IconButton(
@ -139,7 +135,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
void _shareSelected(BuildContext context) {
shareMultiple(context, _selectedFiles.toList());
shareMultiple(context, widget.selectedFiles.files.toList());
}
void _showDeleteSheet(BuildContext context) {
@ -173,10 +169,10 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
_deleteSelected(BuildContext context, bool deleteEverywhere) async {
final dialog = createProgressDialog(context, "Deleting...");
await dialog.show();
await PhotoManager.editor
.deleteWithIds(_selectedFiles.map((p) => p.localId).toList());
await PhotoManager.editor.deleteWithIds(
widget.selectedFiles.files.map((p) => p.localId).toList());
for (File file in _selectedFiles) {
for (File file in widget.selectedFiles.files) {
deleteEverywhere
? await FileDB.instance.markForDeletion(file)
: await FileDB.instance.delete(file);
@ -188,10 +184,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
void _clearSelectedFiles() {
widget.gallery.clearSelection();
setState(() {
_selectedFiles.clear();
});
widget.selectedFiles.clearAll();
}
void _openSyncConfiguration(BuildContext context) {

View file

@ -9,6 +9,7 @@ import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/filters/important_items_filter.dart';
import 'package:photos/models/file.dart';
import 'package:photos/file_repository.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/photo_sync_manager.dart';
import 'package:photos/ui/device_folders_gallery_widget.dart';
import 'package:photos/ui/gallery.dart';
@ -34,10 +35,10 @@ class _HomeWidgetState extends State<HomeWidget> {
final _logger = Logger("HomeWidgetState");
final _remoteFolderGalleryWidget = RemoteFolderGalleryWidget();
final _deviceFolderGalleryWidget = DeviceFolderGalleryWidget();
final _selectedFiles = SelectedFiles();
ShakeDetector _detector;
int _selectedNavBarItem = 0;
Set<File> _selectedPhotos = HashSet<File>();
StreamSubscription<LocalPhotosUpdatedEvent>
_localPhotosUpdatedEventSubscription;
@ -61,9 +62,9 @@ class _HomeWidgetState extends State<HomeWidget> {
var gallery = _getMainGalleryWidget();
return Scaffold(
appBar: GalleryAppBarWidget(
gallery,
GalleryAppBarType.homepage,
widget.title,
_selectedFiles,
"/",
),
bottomNavigationBar: _buildBottomNavigationBar(),
@ -108,6 +109,7 @@ class _HomeWidgetState extends State<HomeWidget> {
reloadEvent: Bus.instance.on<LocalPhotosUpdatedEvent>(),
onRefresh: PhotoSyncManager.instance.sync,
tagPrefix: "home_gallery",
selectedFiles: _selectedFiles,
);
}
@ -149,12 +151,6 @@ class _HomeWidgetState extends State<HomeWidget> {
return filteredPhotos;
}
void _clearSelectedPhotos() {
setState(() {
_selectedPhotos.clear();
});
}
@override
void dispose() {
_detector.stopListening();

View file

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:photos/models/location.dart';
import 'package:photos/models/file.dart';
import 'package:photos/file_repository.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/gallery.dart';
class ViewPort {
@ -30,6 +31,8 @@ class LocationSearchResultsPage extends StatefulWidget {
}
class _LocationSearchResultsPageState extends State<LocationSearchResultsPage> {
final _selectedFiles = SelectedFiles();
@override
Widget build(BuildContext context) {
return Scaffold(
@ -40,6 +43,7 @@ class _LocationSearchResultsPageState extends State<LocationSearchResultsPage> {
child: Gallery(
syncLoader: _getResult,
tagPrefix: "location_search",
selectedFiles: _selectedFiles,
),
),
);

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:photos/db/file_db.dart';
import 'package:photos/folder_service.dart';
import 'package:photos/models/folder.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/gallery.dart';
import 'package:photos/ui/gallery_app_bar_widget.dart';
@ -15,6 +16,8 @@ class RemoteFolderPage extends StatefulWidget {
}
class _RemoteFolderPageState extends State<RemoteFolderPage> {
final _selectedFiles = SelectedFiles();
@override
Widget build(Object context) {
var gallery = Gallery(
@ -22,12 +25,13 @@ class _RemoteFolderPageState extends State<RemoteFolderPage> {
FileDB.instance.getAllInFolder(widget.folder.id, offset, limit),
onRefresh: () => FolderSharingService.instance.syncDiff(widget.folder),
tagPrefix: "remote_folder",
selectedFiles: _selectedFiles,
);
return Scaffold(
appBar: GalleryAppBarWidget(
gallery,
GalleryAppBarType.remote_folder,
widget.folder.name,
_selectedFiles,
widget.folder.deviceFolder,
),
body: gallery,