[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:
ashilkn
2024-10-02 10:05:26 +05:30
parent 2292146706
commit 007f7aa5d6
6 changed files with 162 additions and 57 deletions

View File

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

View File

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

View File

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

View 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,
);
}
}

View File

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

View File

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