diff --git a/mobile/apps/photos/lib/models/gallery/gallery_sections.dart b/mobile/apps/photos/lib/models/gallery/gallery_sections.dart index 5b418d4365..34d8fa79c6 100644 --- a/mobile/apps/photos/lib/models/gallery/gallery_sections.dart +++ b/mobile/apps/photos/lib/models/gallery/gallery_sections.dart @@ -14,6 +14,9 @@ import "package:photos/ui/viewer/gallery/component/group/type.dart"; import "package:photos/ui/viewer/gallery/scrollbar/custom_scroll_bar_2.dart"; import "package:uuid/uuid.dart"; +/// In order to make the gallery performant when GroupTypes do not show group +/// headers, groups are still created here but with the group header replaced by +/// the grid's main axis spacing. class GalleryGroups { final List allFiles; final GroupType groupType; @@ -26,7 +29,7 @@ class GalleryGroups { //TODO: Add support for sort order final bool sortOrderAsc; final double widthAvailable; - final double headerExtent; + final double groupHeaderExtent; GalleryGroups({ required this.allFiles, required this.groupType, @@ -34,11 +37,20 @@ class GalleryGroups { required this.selectedFiles, required this.tagPrefix, this.sortOrderAsc = true, - required this.headerExtent, + + /// Should be GroupGallery.spacing if GroupType.showGroupHeader() is false. + required this.groupHeaderExtent, required this.showSelectAllByDefault, this.limitSelectionToOne = false, }) { init(); + if (!groupType.showGroupHeader()) { + assert( + groupHeaderExtent == spacing, + '''groupHeaderExtent should be equal to spacing when group header is not + shown since the header is just replaced by the grid's main axis spacing''', + ); + } } static const double spacing = 2.0; @@ -67,8 +79,8 @@ class GalleryGroups { List get scrollbarDivisions => _scrollbarDivisions; void init() { - _buildGroups(); crossAxisCount = localSettings.getPhotoGridSize(); + _buildGroups(); _groupLayouts = _computeGroupLayouts(); assert(groupIDs.length == _groupIdToFilesMap.length); assert(groupIDs.length == _groupIdToHeaderDataMap.length); @@ -83,6 +95,7 @@ class GalleryGroups { List _computeGroupLayouts() { final stopwatch = Stopwatch()..start(); + final showGroupHeader = groupType.showGroupHeader(); int currentIndex = 0; double currentOffset = 0.0; final tileHeight = @@ -100,7 +113,7 @@ class GalleryGroups { final maxOffset = minOffset + (numberOfGridRows * tileHeight) + (numberOfGridRows - 1) * spacing + - headerExtent; + groupHeaderExtent; final bodyFirstIndex = firstIndex + 1; groupLayouts.add( @@ -109,20 +122,24 @@ class GalleryGroups { lastIndex: lastIndex, minOffset: minOffset, maxOffset: maxOffset, - headerExtent: headerExtent, + headerExtent: groupHeaderExtent, tileHeight: tileHeight, spacing: spacing, builder: (context, rowIndex) { if (rowIndex == firstIndex) { - return GroupHeaderWidget( - title: _groupIdToHeaderDataMap[groupID]! - .groupType - .getTitle(context, groupIDToFilesMap[groupID]!.first), - gridSize: crossAxisCount, - filesInGroup: groupIDToFilesMap[groupID]!, - selectedFiles: selectedFiles, - showSelectAllByDefault: showSelectAllByDefault, - ); + if (showGroupHeader) { + return GroupHeaderWidget( + title: _groupIdToHeaderDataMap[groupID]! + .groupType + .getTitle(context, groupIDToFilesMap[groupID]!.first), + gridSize: crossAxisCount, + filesInGroup: groupIDToFilesMap[groupID]!, + selectedFiles: selectedFiles, + showSelectAllByDefault: showSelectAllByDefault, + ); + } else { + return const SizedBox(height: spacing); + } } else { final gridRowChildren = []; final firstIndexOfRowWrtFilesInGroup = @@ -208,31 +225,47 @@ class GalleryGroups { // TODO: compute this in isolate void _buildGroups() { final stopwatch = Stopwatch()..start(); - final years = {}; + + final yearsInGroups = {}; //Only relevant for time grouping List groupFiles = []; - for (int index = 0; index < allFiles.length; index++) { - if (index > 0 && - !groupType.areFromSameGroup(allFiles[index - 1], allFiles[index])) { - _createNewGroup(groupFiles, years); - groupFiles = []; + final allFilesLength = allFiles.length; + + if (groupType.showGroupHeader()) { + for (int index = 0; index < allFilesLength; index++) { + if (index > 0 && + !groupType.areFromSameGroup(allFiles[index - 1], allFiles[index])) { + _createNewGroup(groupFiles, yearsInGroups); + groupFiles = []; + } + groupFiles.add(allFiles[index]); + } + if (groupFiles.isNotEmpty) { + _createNewGroup(groupFiles, yearsInGroups); + } + } else { +// Split allFiles into groups of max length 10 * crossAxisCount for + // better performance since SectionedSliverList is used. + for (int i = 0; i < allFiles.length; i += 10 * crossAxisCount) { + final end = (i + 10 * crossAxisCount < allFiles.length) + ? i + 10 * crossAxisCount + : allFiles.length; + final subGroup = allFiles.sublist(i, end); + _createNewGroup(subGroup, yearsInGroups); } - groupFiles.add(allFiles[index]); - } - if (groupFiles.isNotEmpty) { - _createNewGroup(groupFiles, years); } + _logger.info( - "Built ${_groupIds.length} groups in ${stopwatch.elapsedMilliseconds} ms", + "Built ${_groupIds.length} groups for group type ${groupType.name} in ${stopwatch.elapsedMilliseconds} ms", ); print( - "Built ${_groupIds.length} groups in ${stopwatch.elapsedMilliseconds} ms", + "Built ${_groupIds.length} groups for group type ${groupType.name} in ${stopwatch.elapsedMilliseconds} ms", ); stopwatch.stop(); } void _createNewGroup( List groupFiles, - Set years, + Set yearsInGroups, ) { final uuid = _uuid.v1(); _groupIds.add(uuid); @@ -241,17 +274,20 @@ class GalleryGroups { groupType: groupType, ); - final yearOfGroup = DateTime.fromMicrosecondsSinceEpoch( - groupFiles.first.creationTime!, - ).year; - if (!years.contains(yearOfGroup)) { - years.add(yearOfGroup); - _scrollbarDivisions.add( - ScrollbarDivision( - groupID: uuid, - title: yearOfGroup.toString(), - ), - ); + // For scrollbar divisions + if (groupType.timeGrouping()) { + final yearOfGroup = DateTime.fromMicrosecondsSinceEpoch( + groupFiles.first.creationTime!, + ).year; + if (!yearsInGroups.contains(yearOfGroup)) { + yearsInGroups.add(yearOfGroup); + _scrollbarDivisions.add( + ScrollbarDivision( + groupID: uuid, + title: yearOfGroup.toString(), + ), + ); + } } } } diff --git a/mobile/apps/photos/lib/ui/viewer/gallery/component/group/type.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/group/type.dart index 40f451b583..26373b6102 100644 --- a/mobile/apps/photos/lib/ui/viewer/gallery/component/group/type.dart +++ b/mobile/apps/photos/lib/ui/viewer/gallery/component/group/type.dart @@ -39,12 +39,9 @@ extension GroupTypeExtension on GroupType { this == GroupType.year; } - bool showGroupHeader() { - if (this == GroupType.size || this == GroupType.none) { - return false; - } - return true; - } + bool showGroupHeader() => timeGrouping(); + + bool showScrollbarDivisions() => timeGrouping(); String getTitle( BuildContext context, diff --git a/mobile/apps/photos/lib/ui/viewer/gallery/gallery.dart b/mobile/apps/photos/lib/ui/viewer/gallery/gallery.dart index 64da0c82b2..479d77b930 100644 --- a/mobile/apps/photos/lib/ui/viewer/gallery/gallery.dart +++ b/mobile/apps/photos/lib/ui/viewer/gallery/gallery.dart @@ -127,9 +127,9 @@ class GalleryState extends State { final _stackKey = GlobalKey(); final _headerKey = GlobalKey(); final _headerHeightNotifier = ValueNotifier(null); - final miscUtil = MiscUtil(); final scrollBarInUseNotifier = ValueNotifier(false); + late GroupType _groupType; @override void initState() { @@ -139,6 +139,7 @@ class GalleryState extends State { "Gallery_${widget.tagPrefix}${kDebugMode ? "_" + widget.albumName! : ""}_x"; _logger = Logger(_logTag); _logger.info("init Gallery"); + _setGroupType(); _debouncer = Debouncer( widget.reloadDebounceTime, executionInterval: widget.reloadDebounceExecutionInterval, @@ -216,20 +217,24 @@ class GalleryState extends State { } }); - getIntrinsicSizeOfWidget( - GroupHeaderWidget( - title: "Dummy title", - gridSize: localSettings.getPhotoGridSize(), - filesInGroup: const [], - selectedFiles: null, - showSelectAllByDefault: false, - ), - context, - ).then((size) { - setState(() { - groupHeaderExtent = size.height; + if (_groupType.showGroupHeader()) { + getIntrinsicSizeOfWidget( + GroupHeaderWidget( + title: "Dummy title", + gridSize: localSettings.getPhotoGridSize(), + filesInGroup: const [], + selectedFiles: null, + showSelectAllByDefault: false, + ), + context, + ).then((size) { + setState(() { + groupHeaderExtent = size.height; + }); }); - }); + } else { + groupHeaderExtent = GalleryGroups.spacing; + } WidgetsBinding.instance.addPostFrameCallback((_) async { try { @@ -249,6 +254,21 @@ class GalleryState extends State { }); } + @override + void didUpdateWidget(covariant Gallery oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.groupType != widget.groupType) { + _setGroupType(); + if (mounted) { + setState(() {}); + } + } + } + + void _setGroupType() { + _groupType = widget.enableFileGrouping ? widget.groupType : GroupType.none; + } + void _setFilesAndReload(List files) { final hasReloaded = _onFilesLoaded(files); if (!hasReloaded && mounted) { @@ -346,7 +366,7 @@ class GalleryState extends State { _allGalleryFiles = files; final updatedGroupedFiles = - widget.enableFileGrouping && widget.groupType.timeGrouping() + widget.enableFileGrouping && _groupType.timeGrouping() ? _groupBasedOnTime(files) : _genericGroupForPerf(files); if (currentGroupedFiles.length != updatedGroupedFiles.length || @@ -427,11 +447,11 @@ class GalleryState extends State { final galleryGroups = GalleryGroups( allFiles: _allGalleryFiles, - groupType: widget.groupType, + groupType: _groupType, widthAvailable: MediaQuery.sizeOf(context).width, selectedFiles: widget.selectedFiles, tagPrefix: widget.tagPrefix, - headerExtent: groupHeaderExtent!, + groupHeaderExtent: groupHeaderExtent!, showSelectAllByDefault: widget.showSelectAllByDefault, ); GalleryFilesState.of(context).setGalleryFiles = _allGalleryFiles; @@ -441,7 +461,7 @@ class GalleryState extends State { return GalleryContextState( sortOrderAsc: _sortOrderAsc, inSelectionMode: widget.inSelectionMode, - type: widget.groupType, + type: _groupType, // Replace this with the new gallery and use `_allGalleryFiles` // child: MultipleGroupsGalleryView( // groupedFiles: currentGroupedFiles, @@ -512,14 +532,16 @@ class GalleryState extends State { ), ], ), - PinnedGroupHeader( - scrollController: _scrollController, - galleryGroups: galleryGroups, - headerHeightNotifier: _headerHeightNotifier, - selectedFiles: widget.selectedFiles, - showSelectAllByDefault: widget.showSelectAllByDefault, - scrollbarInUseNotifier: scrollBarInUseNotifier, - ), + galleryGroups.groupType.showGroupHeader() + ? PinnedGroupHeader( + scrollController: _scrollController, + galleryGroups: galleryGroups, + headerHeightNotifier: _headerHeightNotifier, + selectedFiles: widget.selectedFiles, + showSelectAllByDefault: widget.showSelectAllByDefault, + scrollbarInUseNotifier: scrollBarInUseNotifier, + ) + : const SizedBox.shrink(), ], ), ), @@ -529,7 +551,7 @@ class GalleryState extends State { // create groups of 200 files for performance List> _genericGroupForPerf(List files) { - if (widget.groupType == GroupType.size) { + if (_groupType == GroupType.size) { // sort files by fileSize on the bases of _sortOrderAsc files.sort((a, b) { if (_sortOrderAsc) { @@ -542,7 +564,7 @@ class GalleryState extends State { // todo:(neeraj) Stick to default group behaviour for magicSearch and editLocationGallery // In case of Magic search, we need to hide the scrollbar title (can be done // by specifying none as groupType) - if (widget.groupType != GroupType.size) { + if (_groupType != GroupType.size) { return [files]; } @@ -770,7 +792,7 @@ class _PinnedGroupHeaderState extends State { .first, ), gridSize: localSettings.getPhotoGridSize(), - height: widget.galleryGroups.headerExtent, + height: widget.galleryGroups.groupHeaderExtent, filesInGroup: widget .galleryGroups.groupIDToFilesMap[currentGroupId!]!, selectedFiles: widget.selectedFiles, diff --git a/mobile/apps/photos/lib/ui/viewer/gallery/scrollbar/custom_scroll_bar_2.dart b/mobile/apps/photos/lib/ui/viewer/gallery/scrollbar/custom_scroll_bar_2.dart index 4a16a40441..0af8f8e5e4 100644 --- a/mobile/apps/photos/lib/ui/viewer/gallery/scrollbar/custom_scroll_bar_2.dart +++ b/mobile/apps/photos/lib/ui/viewer/gallery/scrollbar/custom_scroll_bar_2.dart @@ -3,6 +3,7 @@ import "package:flutter/material.dart"; import "package:logging/logging.dart"; import "package:photos/models/gallery/gallery_sections.dart"; import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/viewer/gallery/component/group/type.dart"; import "package:photos/ui/viewer/gallery/scrollbar/scroll_bar_with_use_notifier.dart"; import "package:photos/utils/misc_util.dart"; import "package:photos/utils/widget_util.dart"; @@ -81,8 +82,9 @@ class _CustomScrollBar2State extends State { void _init() { _logger.info("Initializing CustomScrollBar2"); - if (widget.galleryGroups.groupLayouts.last.maxOffset > - widget.heighOfViewport * 5) { + if (widget.galleryGroups.groupType.showScrollbarDivisions() && + widget.galleryGroups.groupLayouts.last.maxOffset > + widget.heighOfViewport * 5) { _showScrollbarDivisions = true; } else { _showScrollbarDivisions = false;