Add support for GroupType.none

This commit is contained in:
ashilkn
2025-07-18 14:36:42 +05:30
parent 9b289d7845
commit a10dcd01b0
4 changed files with 132 additions and 75 deletions

View File

@@ -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<EnteFile> 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<ScrollbarDivision> 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<FixedExtentSectionLayout> _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 = <Widget>[];
final firstIndexOfRowWrtFilesInGroup =
@@ -208,31 +225,47 @@ class GalleryGroups {
// TODO: compute this in isolate
void _buildGroups() {
final stopwatch = Stopwatch()..start();
final years = <int>{};
final yearsInGroups = <int>{}; //Only relevant for time grouping
List<EnteFile> 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<EnteFile> groupFiles,
Set<int> years,
Set<int> 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(),
),
);
}
}
}
}

View File

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

View File

@@ -127,9 +127,9 @@ class GalleryState extends State<Gallery> {
final _stackKey = GlobalKey();
final _headerKey = GlobalKey();
final _headerHeightNotifier = ValueNotifier<double?>(null);
final miscUtil = MiscUtil();
final scrollBarInUseNotifier = ValueNotifier<bool>(false);
late GroupType _groupType;
@override
void initState() {
@@ -139,6 +139,7 @@ class GalleryState extends State<Gallery> {
"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<Gallery> {
}
});
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<Gallery> {
});
}
@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<EnteFile> files) {
final hasReloaded = _onFilesLoaded(files);
if (!hasReloaded && mounted) {
@@ -346,7 +366,7 @@ class GalleryState extends State<Gallery> {
_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<Gallery> {
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<Gallery> {
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<Gallery> {
),
],
),
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<Gallery> {
// create groups of 200 files for performance
List<List<EnteFile>> _genericGroupForPerf(List<EnteFile> 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<Gallery> {
// 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<PinnedGroupHeader> {
.first,
),
gridSize: localSettings.getPhotoGridSize(),
height: widget.galleryGroups.headerExtent,
height: widget.galleryGroups.groupHeaderExtent,
filesInGroup: widget
.galleryGroups.groupIDToFilesMap[currentGroupId!]!,
selectedFiles: widget.selectedFiles,

View File

@@ -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<CustomScrollBar2> {
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;