[mob][photos] Create a new widget 'HierarchicalSearchGallery' and use it when the first filter is added instead of handling everything in the 'Gallery' widget
Using a separate gallery for Hierarchical Search makes it easier to plug this in in different galleries
This commit is contained in:
@@ -19,6 +19,7 @@ import "package:photos/ui/viewer/gallery/empty_album_state.dart";
|
||||
import 'package:photos/ui/viewer/gallery/empty_state.dart';
|
||||
import 'package:photos/ui/viewer/gallery/gallery.dart';
|
||||
import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
|
||||
import "package:photos/ui/viewer/gallery/hierarchical_search_gallery.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_files_inherited_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/inherited_search_filter_data.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/search_filter_data_provider.dart";
|
||||
@@ -114,7 +115,7 @@ class CollectionPage extends StatelessWidget {
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(50.0),
|
||||
preferredSize: const Size.fromHeight(90.0),
|
||||
child: GalleryAppBarWidget(
|
||||
galleryType,
|
||||
c.collection.displayName,
|
||||
@@ -134,7 +135,23 @@ class CollectionPage extends StatelessWidget {
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
gallery,
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: InheritedSearchFilterData.of(context)
|
||||
.searchFilterDataProvider!
|
||||
.isSearchingNotifier,
|
||||
builder: (context, value, _) {
|
||||
return value
|
||||
? HierarchicalSearchGallery(
|
||||
tagPrefix: tagPrefix,
|
||||
selectedFiles: _selectedFiles,
|
||||
)
|
||||
: gallery;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
FileSelectionOverlayBar(
|
||||
galleryType,
|
||||
_selectedFiles,
|
||||
|
||||
@@ -18,7 +18,6 @@ import 'package:photos/ui/viewer/gallery/empty_state.dart';
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_files_inherited_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/inherited_search_filter_data.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/search_filter_data_provider.dart";
|
||||
import "package:photos/utils/debouncer.dart";
|
||||
import "package:photos/utils/hierarchical_search_util.dart";
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
@@ -114,7 +113,6 @@ class GalleryState extends State<Gallery> {
|
||||
late String _logTag;
|
||||
bool _sortOrderAsc = false;
|
||||
List<EnteFile> _allGalleryFiles = [];
|
||||
late SearchFilterDataProvider? _searchFilterDataProvider;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -185,49 +183,6 @@ class GalleryState extends State<Gallery> {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_searchFilterDataProvider =
|
||||
InheritedSearchFilterData.maybeOf(context)?.searchFilterDataProvider;
|
||||
|
||||
if (_searchFilterDataProvider != null) {
|
||||
_searchFilterDataProvider!
|
||||
.removeListener(fromApplied: true, listener: _onFiltersUpdated);
|
||||
_searchFilterDataProvider!
|
||||
.addListener(toApplied: true, listener: _onFiltersUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFiltersUpdated() async {
|
||||
final filters = _searchFilterDataProvider!.appliedFilters;
|
||||
if (filters.isEmpty) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
final filterdFiles = await getFilteredFiles(filters);
|
||||
_setFilteredFilesAndReload(filterdFiles);
|
||||
curateAlbumFilters(_searchFilterDataProvider!, filterdFiles);
|
||||
}
|
||||
|
||||
void _setFilteredFilesAndReload(List<EnteFile> files) {
|
||||
final updatedGroupedFiles =
|
||||
widget.enableFileGrouping && widget.groupType.timeGrouping()
|
||||
? _groupBasedOnTime(files)
|
||||
: _genericGroupForPerf(files);
|
||||
|
||||
_allGalleryFiles = [
|
||||
for (List<EnteFile> group in updatedGroupedFiles) ...group,
|
||||
];
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
currentGroupedFiles = updatedGroupedFiles;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _setFilesAndReload(List<EnteFile> files) {
|
||||
final hasReloaded = _onFilesLoaded(files);
|
||||
if (!hasReloaded && mounted) {
|
||||
@@ -279,11 +234,13 @@ class GalleryState extends State<Gallery> {
|
||||
"ms",
|
||||
);
|
||||
|
||||
/// To curate filters when a gallery is first opened.
|
||||
if (!result.hasMore) {
|
||||
final searchFilterDataProvider =
|
||||
InheritedSearchFilterData.maybeOf(context)
|
||||
?.searchFilterDataProvider;
|
||||
if (searchFilterDataProvider != null) {
|
||||
if (searchFilterDataProvider != null &&
|
||||
!searchFilterDataProvider.isSearchingNotifier.value) {
|
||||
curateAlbumFilters(searchFilterDataProvider, result.files);
|
||||
}
|
||||
}
|
||||
@@ -303,10 +260,6 @@ class GalleryState extends State<Gallery> {
|
||||
subscription.cancel();
|
||||
}
|
||||
_debouncer.cancelDebounceTimer();
|
||||
if (_searchFilterDataProvider != null) {
|
||||
_searchFilterDataProvider!
|
||||
.removeListener(fromApplied: true, listener: _onFiltersUpdated);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -148,14 +148,14 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 300,
|
||||
width: 200,
|
||||
height: 50,
|
||||
child: AppliedFilters(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: _getDefaultActions(context),
|
||||
// actions: _getDefaultActions(context),
|
||||
bottom: galleryType == GalleryType.searchResults ||
|
||||
galleryType == GalleryType.ownedCollection
|
||||
? const PreferredSize(
|
||||
|
||||
131
mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart
Normal file
131
mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import "dart:async";
|
||||
import "dart:developer";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/events/files_updated_event.dart";
|
||||
import "package:photos/events/local_photos_updated_event.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/models/file_load_result.dart";
|
||||
import "package:photos/models/selected_files.dart";
|
||||
import "package:photos/ui/viewer/gallery/gallery.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_files_inherited_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/inherited_search_filter_data.dart";
|
||||
import "package:photos/ui/viewer/gallery/state/search_filter_data_provider.dart";
|
||||
import "package:photos/utils/hierarchical_search_util.dart";
|
||||
|
||||
class HierarchicalSearchGallery extends StatefulWidget {
|
||||
final String tagPrefix;
|
||||
final SelectedFiles? selectedFiles;
|
||||
const HierarchicalSearchGallery({
|
||||
required this.tagPrefix,
|
||||
this.selectedFiles,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HierarchicalSearchGallery> createState() =>
|
||||
_HierarchicalSearchGalleryState();
|
||||
}
|
||||
|
||||
class _HierarchicalSearchGalleryState extends State<HierarchicalSearchGallery> {
|
||||
StreamSubscription<LocalPhotosUpdatedEvent>? _filesUpdatedEvent;
|
||||
late SearchFilterDataProvider? _searchFilterDataProvider;
|
||||
List<EnteFile> _filterdFiles = <EnteFile>[];
|
||||
int _filteredFilesVersion = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
if (_filesUpdatedEvent != null) {
|
||||
_filesUpdatedEvent!.cancel();
|
||||
}
|
||||
_filesUpdatedEvent =
|
||||
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
|
||||
if (event.type == EventType.deletedFromDevice ||
|
||||
event.type == EventType.deletedFromEverywhere ||
|
||||
event.type == EventType.deletedFromRemote ||
|
||||
event.type == EventType.hide) {
|
||||
for (var updatedFile in event.updatedFiles) {
|
||||
_filterdFiles.remove(updatedFile);
|
||||
GalleryFilesState.of(context).galleryFiles.remove(updatedFile);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
|
||||
_searchFilterDataProvider = InheritedSearchFilterData.maybeOf(context)
|
||||
?.searchFilterDataProvider;
|
||||
|
||||
if (_searchFilterDataProvider != null) {
|
||||
_searchFilterDataProvider!
|
||||
.removeListener(fromApplied: true, listener: _onFiltersUpdated);
|
||||
_searchFilterDataProvider!
|
||||
.addListener(toApplied: true, listener: _onFiltersUpdated);
|
||||
}
|
||||
_onFiltersUpdated();
|
||||
} catch (e) {
|
||||
log('An error occurred: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onFiltersUpdated() async {
|
||||
final filters = _searchFilterDataProvider!.appliedFilters;
|
||||
if (filters.isEmpty) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
final filterdFiles = await getFilteredFiles(filters);
|
||||
_setFilteredFilesAndReload(filterdFiles);
|
||||
curateAlbumFilters(_searchFilterDataProvider!, filterdFiles);
|
||||
}
|
||||
|
||||
void _setFilteredFilesAndReload(List<EnteFile> files) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_filterdFiles = files;
|
||||
GalleryFilesState.of(context).setGalleryFiles = files;
|
||||
_filteredFilesVersion++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_filesUpdatedEvent?.cancel();
|
||||
if (_searchFilterDataProvider != null) {
|
||||
_searchFilterDataProvider!
|
||||
.removeListener(fromApplied: true, listener: _onFiltersUpdated);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Gallery(
|
||||
key: ValueKey(_filteredFilesVersion),
|
||||
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
|
||||
final files = _filterdFiles
|
||||
.where(
|
||||
(file) =>
|
||||
file.creationTime! >= creationStartTime &&
|
||||
file.creationTime! <= creationEndTime,
|
||||
)
|
||||
.toList();
|
||||
return FileLoadResult(files, false);
|
||||
},
|
||||
tagPrefix: widget.tagPrefix,
|
||||
reloadEvent: Bus.instance.on<LocalPhotosUpdatedEvent>(),
|
||||
removalEventTypes: const {
|
||||
EventType.deletedFromRemote,
|
||||
EventType.deletedFromEverywhere,
|
||||
EventType.hide,
|
||||
},
|
||||
selectedFiles: widget.selectedFiles,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,10 @@ class GalleryFilesState extends InheritedWidget {
|
||||
_galleryFiles = galleryFiles;
|
||||
}
|
||||
|
||||
void removeFile(EnteFile file) {
|
||||
_galleryFiles!.remove(file);
|
||||
}
|
||||
|
||||
List<EnteFile> get galleryFiles {
|
||||
if (_galleryFiles == null) {
|
||||
throw Exception(
|
||||
|
||||
@@ -8,7 +8,7 @@ class SearchFilterDataProvider {
|
||||
//TODO: Make this non-nullable and required so every time this is wrapped
|
||||
//over a gallery's scaffold, it's forced to provide an initial gallery filter
|
||||
HierarchicalSearchFilter? initialGalleryFilter;
|
||||
bool _isSearching = false;
|
||||
final isSearchingNotifier = ValueNotifier(false);
|
||||
|
||||
List<HierarchicalSearchFilter> get recommendations =>
|
||||
_recommendedFiltersNotifier.recommendedFilters;
|
||||
@@ -22,8 +22,8 @@ class SearchFilterDataProvider {
|
||||
|
||||
void applyFilters(List<HierarchicalSearchFilter> filters) {
|
||||
_recommendedFiltersNotifier.removeFilters(filters);
|
||||
if (!_isSearching) {
|
||||
_isSearching = true;
|
||||
if (!isSearchingNotifier.value) {
|
||||
isSearchingNotifier.value = true;
|
||||
_appliedFiltersNotifier.addFilters([initialGalleryFilter!, ...filters]);
|
||||
} else {
|
||||
_appliedFiltersNotifier.addFilters(filters);
|
||||
|
||||
Reference in New Issue
Block a user