Merge bcf3084d97
into e203a8378e
This commit is contained in:
commit
7b1b005039
6 changed files with 234 additions and 51 deletions
|
@ -1,16 +1,15 @@
|
|||
import "package:flutter/cupertino.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import 'package:photos/core/constants.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
|
||||
class GroupHeaderWidget extends StatelessWidget {
|
||||
final int timestamp;
|
||||
final String title;
|
||||
final int gridSize;
|
||||
|
||||
const GroupHeaderWidget({
|
||||
super.key,
|
||||
required this.timestamp,
|
||||
required this.title,
|
||||
required this.gridSize,
|
||||
});
|
||||
|
||||
|
@ -22,7 +21,7 @@ class GroupHeaderWidget extends StatelessWidget {
|
|||
gridSize < photoGridSizeMax ? textTheme.body : textTheme.small;
|
||||
final double horizontalPadding = gridSize < photoGridSizeMax ? 12.0 : 8.0;
|
||||
final double verticalPadding = gridSize < photoGridSizeMax ? 12.0 : 14.0;
|
||||
final String dayTitle = _getDayTitle(context, timestamp);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding,
|
||||
|
@ -31,33 +30,12 @@ class GroupHeaderWidget extends StatelessWidget {
|
|||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
dayTitle,
|
||||
style: (dayTitle == S.of(context).dayToday)
|
||||
title,
|
||||
style: (title == S.of(context).dayToday)
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: colorScheme.textMuted),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getDayTitle(BuildContext context, int timestamp) {
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(timestamp);
|
||||
final now = DateTime.now();
|
||||
|
||||
if (date.year == now.year && date.month == now.month) {
|
||||
if (date.day == now.day) {
|
||||
return S.of(context).dayToday;
|
||||
} else if (date.day == now.day - 1) {
|
||||
return S.of(context).dayYesterday;
|
||||
}
|
||||
}
|
||||
|
||||
if (date.year != DateTime.now().year) {
|
||||
return DateFormat.yMMMEd(Localizations.localeOf(context).languageCode)
|
||||
.format(date);
|
||||
} else {
|
||||
return DateFormat.MMMEd(Localizations.localeOf(context).languageCode)
|
||||
.format(date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import 'package:photos/theme/ente_theme.dart';
|
|||
import "package:photos/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/group_gallery.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/group_header_widget.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/type.dart";
|
||||
import 'package:photos/ui/viewer/gallery/gallery.dart';
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
|
||||
|
||||
|
@ -104,32 +105,30 @@ class _LazyGroupGalleryState extends State<LazyGroupGallery> {
|
|||
if (_filesInGroup.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final DateTime groupDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(_filesInGroup[0].creationTime!);
|
||||
final galleryState = context.findAncestorStateOfType<GalleryState>();
|
||||
final groupType = GalleryContextState.of(context)!.type;
|
||||
|
||||
// iterate over files and check if any of the belongs to this group
|
||||
final anyCandidateForGroup = event.updatedFiles.any((file) {
|
||||
final fileDate = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
return fileDate.year == groupDate.year &&
|
||||
fileDate.month == groupDate.month &&
|
||||
fileDate.day == groupDate.day;
|
||||
});
|
||||
final anyCandidateForGroup = groupType.areModifiedFilesPartOfGroup(
|
||||
event.updatedFiles,
|
||||
_filesInGroup[0],
|
||||
lastFile: _filesInGroup.last,
|
||||
);
|
||||
if (anyCandidateForGroup) {
|
||||
late int startRange, endRange;
|
||||
(startRange, endRange) = groupType.getGroupRange(_filesInGroup[0]);
|
||||
if (kDebugMode) {
|
||||
_logger.info(
|
||||
" files were updated due to ${event.reason} on " +
|
||||
DateTime.fromMicrosecondsSinceEpoch(
|
||||
groupDate.microsecondsSinceEpoch,
|
||||
).toIso8601String(),
|
||||
" files were updated due to ${event.reason} on type ${groupType.name} from ${DateTime.fromMicrosecondsSinceEpoch(startRange).toIso8601String()}"
|
||||
" to ${DateTime.fromMicrosecondsSinceEpoch(endRange).toIso8601String()}",
|
||||
);
|
||||
}
|
||||
if (event.type == EventType.addedOrUpdated ||
|
||||
widget.removalEventTypes.contains(event.type)) {
|
||||
// We are reloading the whole group
|
||||
final dayStartTime =
|
||||
DateTime(groupDate.year, groupDate.month, groupDate.day);
|
||||
final result = await widget.asyncLoader(
|
||||
dayStartTime.microsecondsSinceEpoch,
|
||||
dayStartTime.microsecondsSinceEpoch + microSecondsInDay - 1,
|
||||
startRange,
|
||||
endRange,
|
||||
asc: GalleryContextState.of(context)!.sortOrderAsc,
|
||||
);
|
||||
|
||||
|
@ -144,7 +143,7 @@ class _LazyGroupGalleryState extends State<LazyGroupGallery> {
|
|||
|
||||
//[galleryState] will never be null except when LazyLoadingGallery is
|
||||
//used without Gallery as an ancestor.
|
||||
final galleryState = context.findAncestorStateOfType<GalleryState>();
|
||||
|
||||
if (galleryState?.mounted ?? false) {
|
||||
galleryState!.setState(() {});
|
||||
_filesInGroup = result.files;
|
||||
|
@ -178,6 +177,7 @@ class _LazyGroupGalleryState extends State<LazyGroupGallery> {
|
|||
if (_filesInGroup.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final groupType = GalleryContextState.of(context)!.type;
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
|
@ -185,7 +185,11 @@ class _LazyGroupGalleryState extends State<LazyGroupGallery> {
|
|||
children: [
|
||||
if (widget.enableFileGrouping)
|
||||
GroupHeaderWidget(
|
||||
timestamp: _filesInGroup[0].creationTime!,
|
||||
title: groupType.getTitle(
|
||||
context,
|
||||
_filesInGroup[0],
|
||||
lastFile: _filesInGroup.last,
|
||||
),
|
||||
gridSize: widget.photoGridSize,
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
|
|
175
mobile/lib/ui/viewer/gallery/component/group/type.dart
Normal file
175
mobile/lib/ui/viewer/gallery/component/group/type.dart
Normal file
|
@ -0,0 +1,175 @@
|
|||
import "package:flutter/widgets.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/utils/date_time_util.dart";
|
||||
|
||||
enum GroupType { day, week, month, size, year }
|
||||
|
||||
extension GroupTypeExtension on GroupType {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case GroupType.day:
|
||||
return "day";
|
||||
case GroupType.week:
|
||||
return "week";
|
||||
case GroupType.month:
|
||||
return "month";
|
||||
case GroupType.size:
|
||||
return "size";
|
||||
case GroupType.year:
|
||||
return "year";
|
||||
}
|
||||
}
|
||||
|
||||
String getTitle(BuildContext context, EnteFile file, {EnteFile? lastFile}) {
|
||||
if (this == GroupType.day) {
|
||||
return _getDayTitle(context, file.creationTime!);
|
||||
} else if (this == GroupType.week) {
|
||||
// return weeks starting date to end date based on file
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
final startOfWeek = date.subtract(Duration(days: date.weekday - 1));
|
||||
final endOfWeek = startOfWeek.add(const Duration(days: 6));
|
||||
return "${DateFormat.MMMd(Localizations.localeOf(context).languageCode).format(startOfWeek)} - ${DateFormat.MMMd(Localizations.localeOf(context).languageCode).format(endOfWeek)}, ${endOfWeek.year}";
|
||||
} else if (this == GroupType.year) {
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
return DateFormat.y(Localizations.localeOf(context).languageCode)
|
||||
.format(date);
|
||||
} else if (this == GroupType.month) {
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
return DateFormat.yMMM(Localizations.localeOf(context).languageCode)
|
||||
.format(date);
|
||||
} else {
|
||||
throw UnimplementedError("not implemented for $this");
|
||||
}
|
||||
}
|
||||
|
||||
// returns true if the group should be refreshed.
|
||||
// If groupType is day, it should return true if the list of modified files contains a file that was created on the same day as the first file.
|
||||
// If groupType is week, it should return true if the list of modified files contains a file that was created in the same week as the first file.
|
||||
// If groupType is month, it should return true if the list of modified files contains a file that was created in the same month as the first file.
|
||||
// If groupType is year, it should return true if the list of modified files contains a file that was created in the same year as the first file.
|
||||
bool areModifiedFilesPartOfGroup(
|
||||
List<EnteFile> modifiedFiles,
|
||||
EnteFile fistFile, {
|
||||
EnteFile? lastFile,
|
||||
}) {
|
||||
switch (this) {
|
||||
case GroupType.day:
|
||||
return modifiedFiles.any(
|
||||
(file) => areFromSameDay(fistFile.creationTime!, file.creationTime!),
|
||||
);
|
||||
case GroupType.week:
|
||||
return modifiedFiles.any((file) {
|
||||
final firstDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(fistFile.creationTime!);
|
||||
final fileDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
return areDatesInSameWeek(firstDate, fileDate);
|
||||
});
|
||||
case GroupType.month:
|
||||
return modifiedFiles.any((file) {
|
||||
final firstDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(fistFile.creationTime!);
|
||||
final fileDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
return firstDate.year == fileDate.year &&
|
||||
firstDate.month == fileDate.month;
|
||||
});
|
||||
case GroupType.year:
|
||||
return modifiedFiles.any((file) {
|
||||
final firstDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(fistFile.creationTime!);
|
||||
final fileDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
return firstDate.year == fileDate.year;
|
||||
});
|
||||
default:
|
||||
throw UnimplementedError("not implemented for $this");
|
||||
}
|
||||
}
|
||||
|
||||
// for day, year, month, year type, return the microsecond range of the group
|
||||
(int, int) getGroupRange(EnteFile file) {
|
||||
switch (this) {
|
||||
case GroupType.day:
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
final startOfDay = DateTime(date.year, date.month, date.day);
|
||||
return (
|
||||
startOfDay.microsecondsSinceEpoch,
|
||||
(startOfDay.microsecondsSinceEpoch + microSecondsInDay - 1),
|
||||
);
|
||||
case GroupType.week:
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
final startOfWeek = DateTime(date.year, date.month, date.day)
|
||||
.subtract(Duration(days: date.weekday - 1));
|
||||
final endOfWeek = startOfWeek.add(const Duration(days: 7));
|
||||
return (
|
||||
startOfWeek.microsecondsSinceEpoch,
|
||||
endOfWeek.microsecondsSinceEpoch - 1
|
||||
);
|
||||
case GroupType.month:
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
final startOfMonth = DateTime(date.year, date.month);
|
||||
final endOfMonth = DateTime(date.year, date.month + 1);
|
||||
return (
|
||||
startOfMonth.microsecondsSinceEpoch,
|
||||
endOfMonth.microsecondsSinceEpoch - 1
|
||||
);
|
||||
case GroupType.year:
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
|
||||
final startOfYear = DateTime(date.year);
|
||||
final endOfYear = DateTime(date.year + 1);
|
||||
return (
|
||||
startOfYear.microsecondsSinceEpoch,
|
||||
endOfYear.microsecondsSinceEpoch - 1
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError("not implemented for $this");
|
||||
}
|
||||
}
|
||||
|
||||
bool areFromSameGroup(EnteFile first, EnteFile second) {
|
||||
switch (this) {
|
||||
case GroupType.day:
|
||||
return areFromSameDay(first.creationTime!, second.creationTime!);
|
||||
case GroupType.month:
|
||||
return DateTime.fromMicrosecondsSinceEpoch(first.creationTime!).year ==
|
||||
DateTime.fromMicrosecondsSinceEpoch(second.creationTime!)
|
||||
.year &&
|
||||
DateTime.fromMicrosecondsSinceEpoch(first.creationTime!).month ==
|
||||
DateTime.fromMicrosecondsSinceEpoch(second.creationTime!).month;
|
||||
case GroupType.year:
|
||||
return DateTime.fromMicrosecondsSinceEpoch(first.creationTime!).year ==
|
||||
DateTime.fromMicrosecondsSinceEpoch(second.creationTime!).year;
|
||||
case GroupType.week:
|
||||
final firstDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(first.creationTime!);
|
||||
final secondDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(second.creationTime!);
|
||||
return areDatesInSameWeek(firstDate, secondDate);
|
||||
default:
|
||||
throw UnimplementedError("not implemented for $this");
|
||||
}
|
||||
}
|
||||
|
||||
String _getDayTitle(BuildContext context, int timestamp) {
|
||||
final date = DateTime.fromMicrosecondsSinceEpoch(timestamp);
|
||||
final now = DateTime.now();
|
||||
if (date.year == now.year && date.month == now.month) {
|
||||
if (date.day == now.day) {
|
||||
return S.of(context).dayToday;
|
||||
} else if (date.day == now.day - 1) {
|
||||
return S.of(context).dayYesterday;
|
||||
}
|
||||
}
|
||||
if (date.year != DateTime.now().year) {
|
||||
return DateFormat.yMMMEd(Localizations.localeOf(context).languageCode)
|
||||
.format(date);
|
||||
} else {
|
||||
return DateFormat.MMMEd(Localizations.localeOf(context).languageCode)
|
||||
.format(date);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,10 +12,10 @@ 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/common/loading_widget.dart';
|
||||
import "package:photos/ui/viewer/gallery/component/group/type.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/multiple_groups_gallery_view.dart";
|
||||
import 'package:photos/ui/viewer/gallery/empty_state.dart';
|
||||
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
|
||||
import 'package:photos/utils/date_time_util.dart';
|
||||
import "package:photos/utils/debouncer.dart";
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
|
@ -59,6 +59,7 @@ class Gallery extends StatefulWidget {
|
|||
|
||||
// add a Function variable to get sort value in bool
|
||||
final SortAscFn? sortAsyncFn;
|
||||
final GroupType groupType;
|
||||
|
||||
const Gallery({
|
||||
required this.asyncLoader,
|
||||
|
@ -73,6 +74,7 @@ class Gallery extends StatefulWidget {
|
|||
this.emptyState = const EmptyState(),
|
||||
this.scrollBottomSafeArea = 120.0,
|
||||
this.albumName = '',
|
||||
this.groupType = GroupType.day,
|
||||
this.enableFileGrouping = true,
|
||||
this.loadingWidget = const EnteLoadingWidget(),
|
||||
this.disableScroll = false,
|
||||
|
@ -248,6 +250,7 @@ class GalleryState extends State<Gallery> {
|
|||
return GalleryContextState(
|
||||
sortOrderAsc: _sortOrderAsc,
|
||||
inSelectionMode: widget.inSelectionMode,
|
||||
type: widget.groupType,
|
||||
child: MultipleGroupsGalleryView(
|
||||
itemScroller: _itemScroller,
|
||||
groupedFiles: currentGroupedFiles,
|
||||
|
@ -273,13 +276,11 @@ class GalleryState extends State<Gallery> {
|
|||
|
||||
List<List<EnteFile>> _groupFiles(List<EnteFile> files) {
|
||||
List<EnteFile> dailyFiles = [];
|
||||
|
||||
final List<List<EnteFile>> resultGroupedFiles = [];
|
||||
for (int index = 0; index < files.length; index++) {
|
||||
if (index > 0 &&
|
||||
!areFromSameDay(
|
||||
files[index - 1].creationTime!,
|
||||
files[index].creationTime!,
|
||||
)) {
|
||||
!widget.groupType.areFromSameGroup(files[index - 1], files[index])) {
|
||||
resultGroupedFiles.add(dailyFiles);
|
||||
dailyFiles = [];
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:photos/ui/viewer/gallery/component/group/type.dart";
|
||||
|
||||
class GalleryContextState extends InheritedWidget {
|
||||
///Sorting by creation time
|
||||
final bool sortOrderAsc;
|
||||
final bool inSelectionMode;
|
||||
final GroupType type;
|
||||
|
||||
const GalleryContextState({
|
||||
this.inSelectionMode = false,
|
||||
this.type = GroupType.day,
|
||||
required this.sortOrderAsc,
|
||||
required Widget child,
|
||||
Key? key,
|
||||
|
@ -19,6 +22,7 @@ class GalleryContextState extends InheritedWidget {
|
|||
@override
|
||||
bool updateShouldNotify(GalleryContextState oldWidget) {
|
||||
return sortOrderAsc != oldWidget.sortOrderAsc ||
|
||||
inSelectionMode != oldWidget.inSelectionMode;
|
||||
inSelectionMode != oldWidget.inSelectionMode ||
|
||||
type != oldWidget.type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,27 @@ bool areFromSameDay(int firstCreationTime, int secondCreationTime) {
|
|||
firstDate.day == secondDate.day;
|
||||
}
|
||||
|
||||
bool areDatesInSameWeek(DateTime date1, DateTime date2) {
|
||||
if (date1.year == date2.year &&
|
||||
date1.month == date2.month &&
|
||||
date1.day == date2.day) {
|
||||
return true;
|
||||
}
|
||||
final int dayOfWeek1 = date1.weekday;
|
||||
final int dayOfWeek2 = date2.weekday;
|
||||
// Calculate the start and end dates of the week for both dates
|
||||
final DateTime startOfWeek1 = date1.subtract(Duration(days: dayOfWeek1 - 1));
|
||||
final DateTime endOfWeek1 = startOfWeek1.add(const Duration(days: 6));
|
||||
final DateTime startOfWeek2 = date2.subtract(Duration(days: dayOfWeek2 - 1));
|
||||
final DateTime endOfWeek2 = startOfWeek2.add(const Duration(days: 6));
|
||||
// Check if the two dates fall within the same week range
|
||||
if ((date1.isAfter(startOfWeek2) && date1.isBefore(endOfWeek2)) ||
|
||||
(date2.isAfter(startOfWeek1) && date2.isBefore(endOfWeek1))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create link default names:
|
||||
// Same day: "Dec 19, 2022"
|
||||
// Same month: "Dec 19 - 22, 2022"
|
||||
|
|
Loading…
Add table
Reference in a new issue