import "dart:async"; import 'dart:developer' as dev; import "package:flutter/material.dart"; import "package:photos/core/constants.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/db/files_db.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/gallery_type.dart"; import "package:photos/models/search/hierarchical/hierarchical_search_filter.dart"; import "package:photos/models/search/hierarchical/location_filter.dart"; import "package:photos/models/selected_files.dart"; import "package:photos/services/collections_service.dart"; import "package:photos/services/filter/db_filters.dart"; import "package:photos/services/location_service.dart"; import "package:photos/states/location_screen_state.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/common/loading_widget.dart"; import "package:photos/ui/viewer/actions/file_selection_overlay_bar.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"; import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class LocationScreen extends StatefulWidget { final String tagPrefix; const LocationScreen({this.tagPrefix = "", super.key}); @override State createState() => _LocationScreenState(); } class _LocationScreenState extends State { final selectedFiles = SelectedFiles(); @override Widget build(BuildContext context) { final heightOfStatusBar = MediaQuery.of(context).viewPadding.top; const heightOfAppBar = 90.0; final locationTag = InheritedLocationScreenState.of(context).locationTagEntity.item; return GalleryFilesState( child: InheritedSearchFilterDataWrapper( searchFilterDataProvider: SearchFilterDataProvider( initialGalleryFilter: LocationFilter( locationTag: locationTag, occurrence: kMostRelevantFilter, ), ), child: Scaffold( appBar: PreferredSize( preferredSize: const Size(double.infinity, heightOfAppBar), child: GalleryAppBarWidget( GalleryType.locationTag, locationTag.name, selectedFiles, ), ), body: Column( children: [ SizedBox( height: MediaQuery.of(context).size.height - (heightOfAppBar + heightOfStatusBar), width: double.infinity, child: LocationGalleryWidget( tagPrefix: widget.tagPrefix, selectedFiles: selectedFiles, ), ), ], ), ), ), ); } } class LocationGalleryWidget extends StatefulWidget { final String tagPrefix; final SelectedFiles selectedFiles; const LocationGalleryWidget({ required this.tagPrefix, required this.selectedFiles, super.key, }); @override State createState() => _LocationGalleryWidgetState(); } class _LocationGalleryWidgetState extends State { late final Future fileLoadResult; late final List allFilesWithLocation; final _selectedFiles = SelectedFiles(); late final StreamSubscription _filesUpdateEvent; @override void initState() { super.initState(); final collectionsToHide = CollectionsService.instance.getHiddenCollectionIds(); fileLoadResult = FilesDB.instance .fetchAllUploadedAndSharedFilesWithLocation( galleryLoadStartTime, galleryLoadEndTime, limit: null, asc: false, filterOptions: DBFilterOptions( ignoredCollectionIDs: collectionsToHide, hideIgnoredForUpload: true, ), ) .then((value) { allFilesWithLocation = value.files; _filesUpdateEvent = Bus.instance.on().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) { allFilesWithLocation.remove(updatedFile); } if (mounted) { setState(() {}); } } }); return value; }); } @override void dispose() { InheritedLocationScreenState.memoryCountNotifier.value = null; _filesUpdateEvent.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final selectedRadius = InheritedLocationScreenState.of(context).locationTagEntity.item.radius; final centerPoint = InheritedLocationScreenState.of(context) .locationTagEntity .item .centerPoint; Future filterFiles() async { //waiting for allFilesWithLocation to be initialized await fileLoadResult; final stopWatch = Stopwatch()..start(); final filesInLocation = allFilesWithLocation; filesInLocation.removeWhere((f) { return !isFileInsideLocationTag( centerPoint, f.location!, selectedRadius, ); }); dev.log( "Time taken to get all files in a location tag: ${stopWatch.elapsedMilliseconds} ms", ); stopWatch.stop(); InheritedLocationScreenState.memoryCountNotifier.value = filesInLocation.length; return Future.value( FileLoadResult( filesInLocation, false, ), ); } return FutureBuilder( //rebuild gallery only when there is change in radius or center point key: ValueKey("$centerPoint$selectedRadius"), builder: (context, snapshot) { if (snapshot.hasData) { return SelectionState( selectedFiles: _selectedFiles, child: Stack( alignment: Alignment.bottomCenter, children: [ Builder( builder: (context) { return ValueListenableBuilder( valueListenable: InheritedSearchFilterData.of(context) .searchFilterDataProvider! .isSearchingNotifier, builder: (context, value, _) { return value ? HierarchicalSearchGallery( tagPrefix: widget.tagPrefix, selectedFiles: _selectedFiles, ) : Gallery( loadingWidget: Column( children: [ EnteLoadingWidget( color: getEnteColorScheme(context) .strokeMuted, ), ], ), asyncLoader: ( creationStartTime, creationEndTime, { limit, asc, }) async { return snapshot.data as FileLoadResult; }, reloadEvent: Bus.instance.on(), removalEventTypes: const { EventType.deletedFromRemote, EventType.deletedFromEverywhere, }, selectedFiles: _selectedFiles, tagPrefix: widget.tagPrefix, ); }, ); }, ), FileSelectionOverlayBar( GalleryType.locationTag, _selectedFiles, ), ], ), ); } else { return const Column( children: [ Expanded( child: EnteLoadingWidget(), ), ], ); } }, future: filterFiles(), ); } }