Jelajahi Sumber

Simplify the state management for selected files

Vishnu Mohandas 5 tahun lalu
induk
melakukan
02f7e801c6

+ 22 - 0
lib/models/selected_files.dart

@@ -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();
+  }
+}

+ 4 - 3
lib/ui/device_folder_page.dart

@@ -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,

+ 4 - 1
lib/ui/face_search_results_page.dart

@@ -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,

+ 11 - 27
lib/ui/gallery.dart

@@ -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) {

+ 15 - 22
lib/ui/gallery_app_bar_widget.dart

@@ -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) {

+ 4 - 8
lib/ui/home_widget.dart

@@ -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();

+ 4 - 0
lib/ui/location_search_results_page.dart

@@ -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,
         ),
       ),
     );

+ 5 - 1
lib/ui/remote_folder_page.dart

@@ -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,