ente/lib/ui/viewer/gallery/gallery.dart

291 lines
9.3 KiB
Dart
Raw Normal View History

2020-06-10 18:17:54 +00:00
import 'dart:async';
2020-04-12 12:38:49 +00:00
2022-11-11 09:30:07 +00:00
import 'package:flutter/foundation.dart';
2020-03-28 18:18:27 +00:00
import 'package:flutter/material.dart';
2020-06-17 15:09:47 +00:00
import 'package:logging/logging.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/event_bus.dart';
2021-05-13 11:17:13 +00:00
import 'package:photos/events/event.dart';
import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/tab_changed_event.dart';
2020-06-19 23:03:26 +00:00
import 'package:photos/models/file.dart';
import 'package:photos/models/file_load_result.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/common/loading_widget.dart';
2021-04-20 20:11:39 +00:00
import 'package:photos/ui/huge_listview/huge_listview.dart';
2023-05-31 19:55:57 +00:00
import "package:photos/ui/viewer/gallery/component/multiple_groups_gallery_view.dart";
2022-09-12 12:16:34 +00:00
import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
2020-05-01 18:20:12 +00:00
import 'package:photos/utils/date_time_util.dart';
2020-11-12 13:25:57 +00:00
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
2020-03-28 18:18:27 +00:00
2022-07-03 06:56:43 +00:00
typedef GalleryLoader = Future<FileLoadResult> Function(
int creationStartTime,
int creationEndTime, {
int? limit,
bool? asc,
2022-07-03 06:56:43 +00:00
});
typedef SortAscFn = bool Function();
2020-03-28 18:18:27 +00:00
class Gallery extends StatefulWidget {
final GalleryLoader asyncLoader;
final List<File>? initialFiles;
final Stream<FilesUpdatedEvent>? reloadEvent;
final List<Stream<Event>>? forceReloadEvents;
final Set<EventType> removalEventTypes;
final SelectedFiles? selectedFiles;
final String tagPrefix;
final Widget? header;
final Widget? footer;
final Widget emptyState;
final String? albumName;
final double scrollBottomSafeArea;
2023-05-31 19:08:10 +00:00
final bool enableFileGrouping;
final Widget loadingWidget;
final bool disableScroll;
final bool limitSelectionToOne;
// When true, the gallery will be in selection mode. Tapping on any item
// will select if even when no other item is selected.
final bool inSelectionMode;
final bool showSelectAllByDefault;
final bool isScrollablePositionedList;
// add a Function variable to get sort value in bool
final SortAscFn? sortAsyncFn;
2020-04-14 15:36:18 +00:00
const Gallery({
required this.asyncLoader,
required this.tagPrefix,
this.selectedFiles,
2021-05-13 18:05:32 +00:00
this.initialFiles,
2021-04-20 20:11:39 +00:00
this.reloadEvent,
2021-09-23 11:09:56 +00:00
this.forceReloadEvents,
this.removalEventTypes = const {},
2021-05-12 20:54:44 +00:00
this.header,
2022-08-12 07:13:48 +00:00
this.footer = const SizedBox(height: 120),
this.emptyState = const EmptyState(),
this.scrollBottomSafeArea = 120.0,
2022-04-30 12:18:26 +00:00
this.albumName = '',
2023-05-31 19:08:10 +00:00
this.enableFileGrouping = true,
this.loadingWidget = const EnteLoadingWidget(),
this.disableScroll = false,
this.limitSelectionToOne = false,
this.inSelectionMode = false,
this.sortAsyncFn,
this.showSelectAllByDefault = true,
this.isScrollablePositionedList = true,
Key? key,
2021-09-23 11:09:56 +00:00
}) : super(key: key);
2020-04-14 15:36:18 +00:00
2020-03-28 18:18:27 +00:00
@override
2022-07-03 09:45:00 +00:00
State<Gallery> createState() {
return _GalleryState();
2020-03-28 18:18:27 +00:00
}
}
class _GalleryState extends State<Gallery> {
2021-04-25 12:17:44 +00:00
static const int kInitialLoadLimit = 100;
final _hugeListViewKey = GlobalKey<HugeListViewState>();
late Logger _logger;
2023-06-01 06:35:30 +00:00
List<List<File>> _currentGroupedFiles = [];
bool _hasLoadedFiles = false;
late ItemScrollController _itemScroller;
StreamSubscription<FilesUpdatedEvent>? _reloadEventSubscription;
StreamSubscription<TabDoubleTapEvent>? _tabDoubleTapEvent;
2021-09-23 11:09:56 +00:00
final _forceReloadEventSubscriptions = <StreamSubscription<Event>>[];
late String _logTag;
bool _sortOrderAsc = false;
2020-06-10 18:17:54 +00:00
@override
void initState() {
2022-11-11 09:30:07 +00:00
_logTag =
"Gallery_${widget.tagPrefix}${kDebugMode ? "_" + widget.albumName! : ""}";
2023-05-03 09:52:06 +00:00
_logger = Logger(_logTag);
2022-11-11 09:30:07 +00:00
_logger.finest("init Gallery");
_sortOrderAsc = widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
_itemScroller = ItemScrollController();
2020-06-16 12:56:23 +00:00
if (widget.reloadEvent != null) {
_reloadEventSubscription = widget.reloadEvent!.listen((event) async {
2022-11-12 05:19:38 +00:00
// In soft refresh, setState is called for entire gallery only when
// number of child change
_logger.finest("Soft refresh all files on ${event.reason} ");
final result = await _loadFiles();
2022-11-12 05:19:38 +00:00
final bool hasReloaded = _onFilesLoaded(result.files);
if (hasReloaded && kDebugMode) {
_logger.finest(
"Reloaded gallery on soft refresh all files on ${event.reason}",
);
}
2020-06-15 19:03:43 +00:00
});
2020-06-16 12:56:23 +00:00
}
_tabDoubleTapEvent =
Bus.instance.on<TabDoubleTapEvent>().listen((event) async {
// todo: Assign ID to Gallery and fire generic event with ID &
// target index/date
if (mounted && event.selectedIndex == 0) {
_itemScroller.scrollTo(
2022-10-25 07:20:15 +00:00
index: 0,
duration: const Duration(milliseconds: 150),
);
}
});
2021-09-23 11:09:56 +00:00
if (widget.forceReloadEvents != null) {
for (final event in widget.forceReloadEvents!) {
2022-06-11 08:23:52 +00:00
_forceReloadEventSubscriptions.add(
event.listen((event) async {
2022-11-12 05:19:38 +00:00
_logger.finest("Force refresh all files on ${event.reason}");
_sortOrderAsc =
widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
2022-06-11 08:23:52 +00:00
final result = await _loadFiles();
_setFilesAndReload(result.files);
}),
);
2021-09-23 11:09:56 +00:00
}
2021-05-13 11:17:13 +00:00
}
if (widget.initialFiles != null && !_sortOrderAsc) {
_onFilesLoaded(widget.initialFiles!);
2021-05-13 18:05:32 +00:00
}
_loadFiles(limit: kInitialLoadLimit).then((result) async {
_setFilesAndReload(result.files);
if (result.hasMore) {
final result = await _loadFiles();
_setFilesAndReload(result.files);
}
});
super.initState();
}
void _setFilesAndReload(List<File> files) {
final hasReloaded = _onFilesLoaded(files);
if (!hasReloaded && mounted) {
setState(() {});
}
}
Future<FileLoadResult> _loadFiles({int? limit}) async {
2022-11-12 05:19:38 +00:00
_logger.info("Loading ${limit ?? "all"} files");
try {
2021-09-23 07:31:01 +00:00
final startTime = DateTime.now().microsecondsSinceEpoch;
final result = await widget.asyncLoader(
galleryLoadStartTime,
galleryLoadEndTime,
2022-06-11 08:23:52 +00:00
limit: limit,
asc: _sortOrderAsc,
2022-06-11 08:23:52 +00:00
);
2021-09-23 07:31:01 +00:00
final endTime = DateTime.now().microsecondsSinceEpoch;
final duration = Duration(microseconds: endTime - startTime);
2022-06-11 08:23:52 +00:00
_logger.info(
"Time taken to load " +
result.files.length.toString() +
" files :" +
duration.inMilliseconds.toString() +
"ms",
);
return result;
2021-09-23 07:31:01 +00:00
} catch (e, s) {
_logger.severe("failed to load files", e, s);
rethrow;
}
}
2023-06-01 06:35:30 +00:00
// group files into multiple groups and returns `true` if it resulted in a
// gallery reload
bool _onFilesLoaded(List<File> files) {
2023-06-01 06:35:30 +00:00
final updatedGroupedFiles =
widget.enableFileGrouping ? _groupFiles(files) : [files];
if (_currentGroupedFiles.length != updatedGroupedFiles.length ||
_currentGroupedFiles.isEmpty) {
if (mounted) {
setState(() {
_hasLoadedFiles = true;
2023-06-01 06:35:30 +00:00
_currentGroupedFiles = updatedGroupedFiles;
});
}
2021-05-13 11:17:13 +00:00
return true;
} else {
2023-06-01 06:35:30 +00:00
_currentGroupedFiles = updatedGroupedFiles;
2021-05-13 11:17:13 +00:00
return false;
}
}
2020-11-12 13:25:57 +00:00
@override
void dispose() {
2021-05-13 11:17:13 +00:00
_reloadEventSubscription?.cancel();
_tabDoubleTapEvent?.cancel();
2021-09-23 11:09:56 +00:00
for (final subscription in _forceReloadEventSubscriptions) {
subscription.cancel();
}
2020-11-12 13:25:57 +00:00
super.dispose();
}
2020-03-28 18:18:27 +00:00
@override
Widget build(BuildContext context) {
2022-11-11 09:30:07 +00:00
_logger.finest("Building Gallery ${widget.tagPrefix}");
if (!_hasLoadedFiles) {
return widget.loadingWidget;
}
return GalleryContextState(
sortOrderAsc: _sortOrderAsc,
inSelectionMode: widget.inSelectionMode,
child: MultipleGroupsGalleryView(
hugeListViewKey: _hugeListViewKey,
itemScroller: _itemScroller,
groupedFiles: _currentGroupedFiles,
disableScroll: widget.disableScroll,
emptyState: widget.emptyState,
asyncLoader: widget.asyncLoader,
removalEventTypes: widget.removalEventTypes,
tagPrefix: widget.tagPrefix,
scrollBottomSafeArea: widget.scrollBottomSafeArea,
limitSelectionToOne: widget.limitSelectionToOne,
enableFileGrouping: widget.enableFileGrouping,
logTag: _logTag,
logger: _logger,
reloadEvent: widget.reloadEvent,
header: widget.header,
footer: widget.footer,
selectedFiles: widget.selectedFiles,
showSelectAllByDefault: widget.showSelectAllByDefault,
isScrollablePositionedList: widget.isScrollablePositionedList,
),
);
}
2023-06-01 06:35:30 +00:00
List<List<File>> _groupFiles(List<File> files) {
2023-05-05 06:17:36 +00:00
List<File> dailyFiles = [];
2023-06-01 06:35:30 +00:00
final List<List<File>> resultGroupedFiles = [];
for (int index = 0; index < files.length; index++) {
if (index > 0 &&
!areFromSameDay(
files[index - 1].creationTime!,
files[index].creationTime!,
)) {
2023-06-01 06:35:30 +00:00
resultGroupedFiles.add(dailyFiles);
2023-05-05 06:17:36 +00:00
dailyFiles = [];
}
dailyFiles.add(files[index]);
}
if (dailyFiles.isNotEmpty) {
2023-06-01 06:35:30 +00:00
resultGroupedFiles.add(dailyFiles);
}
if (_sortOrderAsc) {
2023-06-01 06:35:30 +00:00
resultGroupedFiles
2023-05-25 11:43:00 +00:00
.sort((a, b) => a[0].creationTime!.compareTo(b[0].creationTime!));
} else {
2023-06-01 06:35:30 +00:00
resultGroupedFiles
2023-05-25 11:43:00 +00:00
.sort((a, b) => b[0].creationTime!.compareTo(a[0].creationTime!));
}
2023-06-01 06:35:30 +00:00
return resultGroupedFiles;
2020-04-12 12:38:49 +00:00
}
}
class GalleryIndexUpdatedEvent {
final String tag;
final int index;
GalleryIndexUpdatedEvent(this.tag, this.index);
}