|
@@ -1,5 +1,6 @@
|
|
|
import 'dart:math';
|
|
|
|
|
|
+import 'package:collection/collection.dart';
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
import 'package:immich_mobile/shared/models/asset.dart';
|
|
@@ -9,14 +10,15 @@ final log = Logger('AssetGridDataStructure');
|
|
|
|
|
|
enum RenderAssetGridElementType {
|
|
|
assetRow,
|
|
|
- dayTitle,
|
|
|
+ groupDividerTitle,
|
|
|
monthTitle;
|
|
|
}
|
|
|
|
|
|
class RenderAssetGridRow {
|
|
|
final List<Asset> assets;
|
|
|
+ final List<double> widthDistribution;
|
|
|
|
|
|
- RenderAssetGridRow(this.assets);
|
|
|
+ RenderAssetGridRow(this.assets, this.widthDistribution);
|
|
|
}
|
|
|
|
|
|
class RenderAssetGridElement {
|
|
@@ -35,19 +37,36 @@ class RenderAssetGridElement {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+enum GroupAssetsBy {
|
|
|
+ day,
|
|
|
+ month;
|
|
|
+}
|
|
|
+
|
|
|
+class AssetGridLayoutParameters {
|
|
|
+ final int perRow;
|
|
|
+ final bool dynamicLayout;
|
|
|
+ final GroupAssetsBy groupBy;
|
|
|
+
|
|
|
+ AssetGridLayoutParameters(
|
|
|
+ this.perRow,
|
|
|
+ this.dynamicLayout,
|
|
|
+ this.groupBy,
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
class _AssetGroupsToRenderListComputeParameters {
|
|
|
final String monthFormat;
|
|
|
final String dayFormat;
|
|
|
final String dayFormatYear;
|
|
|
- final Map<String, List<Asset>> groups;
|
|
|
- final int perRow;
|
|
|
+ final List<Asset> assets;
|
|
|
+ final AssetGridLayoutParameters layout;
|
|
|
|
|
|
_AssetGroupsToRenderListComputeParameters(
|
|
|
this.monthFormat,
|
|
|
this.dayFormat,
|
|
|
this.dayFormatYear,
|
|
|
- this.groups,
|
|
|
- this.perRow,
|
|
|
+ this.assets,
|
|
|
+ this.layout,
|
|
|
);
|
|
|
}
|
|
|
|
|
@@ -56,62 +75,75 @@ class RenderList {
|
|
|
|
|
|
RenderList(this.elements);
|
|
|
|
|
|
+ static Map<String, List<Asset>> _groupAssets(
|
|
|
+ List<Asset> assets,
|
|
|
+ GroupAssetsBy groupBy,
|
|
|
+ ) {
|
|
|
+ assets.sortByCompare<DateTime>(
|
|
|
+ (e) => e.createdAt,
|
|
|
+ (a, b) => b.compareTo(a),
|
|
|
+ );
|
|
|
+
|
|
|
+ if (groupBy == GroupAssetsBy.day) {
|
|
|
+ return assets.groupListsBy(
|
|
|
+ (element) => DateFormat('y-MM-dd').format(element.createdAt.toLocal()),
|
|
|
+ );
|
|
|
+ } else if (groupBy == GroupAssetsBy.month) {
|
|
|
+ return assets.groupListsBy(
|
|
|
+ (element) => DateFormat('y-MM').format(element.createdAt.toLocal()),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
static Future<RenderList> _processAssetGroupData(
|
|
|
_AssetGroupsToRenderListComputeParameters data,
|
|
|
) async {
|
|
|
final monthFormat = DateFormat(data.monthFormat);
|
|
|
final dayFormatSameYear = DateFormat(data.dayFormat);
|
|
|
final dayFormatOtherYear = DateFormat(data.dayFormatYear);
|
|
|
- final groups = data.groups;
|
|
|
- final perRow = data.perRow;
|
|
|
+ final allAssets = data.assets;
|
|
|
+ final perRow = data.layout.perRow;
|
|
|
+ final dynamicLayout = data.layout.dynamicLayout;
|
|
|
+ final groupBy = data.layout.groupBy;
|
|
|
|
|
|
List<RenderAssetGridElement> elements = [];
|
|
|
DateTime? lastDate;
|
|
|
|
|
|
+ final groups = _groupAssets(allAssets, groupBy);
|
|
|
+
|
|
|
groups.forEach((groupName, assets) {
|
|
|
try {
|
|
|
- final date = DateTime.parse(groupName);
|
|
|
-
|
|
|
- if (lastDate == null || lastDate!.month != date.month) {
|
|
|
- // Month title
|
|
|
-
|
|
|
- var monthTitleText = groupName;
|
|
|
-
|
|
|
- var groupDate = DateTime.tryParse(groupName);
|
|
|
- if (groupDate != null) {
|
|
|
- monthTitleText = monthFormat.format(groupDate);
|
|
|
- } else {
|
|
|
- log.severe("Failed to format date for day title: $groupName");
|
|
|
- }
|
|
|
+ final date = assets.first.createdAt.toLocal();
|
|
|
|
|
|
+ // Month title
|
|
|
+ if (groupBy == GroupAssetsBy.day &&
|
|
|
+ (lastDate == null || lastDate!.month != date.month)) {
|
|
|
elements.add(
|
|
|
RenderAssetGridElement(
|
|
|
RenderAssetGridElementType.monthTitle,
|
|
|
- title: monthTitleText,
|
|
|
+ title: monthFormat.format(date),
|
|
|
date: date,
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // Add group title
|
|
|
- var currentYear = DateTime.now().year;
|
|
|
- var groupYear = DateTime.parse(groupName).year;
|
|
|
- var formatDate =
|
|
|
- currentYear == groupYear ? dayFormatSameYear : dayFormatOtherYear;
|
|
|
+ // Group divider title (day or month)
|
|
|
+ var formatDate = dayFormatOtherYear;
|
|
|
|
|
|
- var dateText = groupName;
|
|
|
+ if (DateTime.now().year == date.year) {
|
|
|
+ formatDate = dayFormatSameYear;
|
|
|
+ }
|
|
|
|
|
|
- var groupDate = DateTime.tryParse(groupName);
|
|
|
- if (groupDate != null) {
|
|
|
- dateText = formatDate.format(groupDate);
|
|
|
- } else {
|
|
|
- log.severe("Failed to format date for day title: $groupName");
|
|
|
+ if (groupBy == GroupAssetsBy.month) {
|
|
|
+ formatDate = monthFormat;
|
|
|
}
|
|
|
|
|
|
elements.add(
|
|
|
RenderAssetGridElement(
|
|
|
- RenderAssetGridElementType.dayTitle,
|
|
|
- title: dateText,
|
|
|
+ RenderAssetGridElementType.groupDividerTitle,
|
|
|
+ title: formatDate.format(date),
|
|
|
date: date,
|
|
|
relatedAssetList: assets,
|
|
|
),
|
|
@@ -121,12 +153,37 @@ class RenderList {
|
|
|
int cursor = 0;
|
|
|
while (cursor < assets.length) {
|
|
|
int rowElements = min(assets.length - cursor, perRow);
|
|
|
+ final rowAssets = assets.sublist(cursor, cursor + rowElements);
|
|
|
+
|
|
|
+ // Default: All assets have the same width
|
|
|
+ var widthDistribution = List.filled(rowElements, 1.0);
|
|
|
+
|
|
|
+ if (dynamicLayout) {
|
|
|
+ final aspectRatios =
|
|
|
+ rowAssets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
|
|
|
+ final meanAspectRatio = aspectRatios.sum / rowElements;
|
|
|
+
|
|
|
+ // 1: mean width
|
|
|
+ // 0.5: width < mean - threshold
|
|
|
+ // 1.5: width > mean + threshold
|
|
|
+ final arConfiguration = aspectRatios.map((e) {
|
|
|
+ if (e - meanAspectRatio > 0.3) return 1.5;
|
|
|
+ if (e - meanAspectRatio < -0.3) return 0.5;
|
|
|
+ return 1.0;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Normalize:
|
|
|
+ final sum = arConfiguration.sum;
|
|
|
+ widthDistribution =
|
|
|
+ arConfiguration.map((e) => (e * rowElements) / sum).toList();
|
|
|
+ }
|
|
|
|
|
|
final rowElement = RenderAssetGridElement(
|
|
|
RenderAssetGridElementType.assetRow,
|
|
|
date: date,
|
|
|
assetRow: RenderAssetGridRow(
|
|
|
- assets.sublist(cursor, cursor + rowElements),
|
|
|
+ rowAssets,
|
|
|
+ widthDistribution,
|
|
|
),
|
|
|
);
|
|
|
|
|
@@ -143,9 +200,9 @@ class RenderList {
|
|
|
return RenderList(elements);
|
|
|
}
|
|
|
|
|
|
- static Future<RenderList> fromAssetGroups(
|
|
|
- Map<String, List<Asset>> assetGroups,
|
|
|
- int assetsPerRow,
|
|
|
+ static Future<RenderList> fromAssets(
|
|
|
+ List<Asset> assets,
|
|
|
+ AssetGridLayoutParameters layout,
|
|
|
) async {
|
|
|
// Compute only allows for one parameter. Therefore we pass all parameters in a map
|
|
|
return compute(
|
|
@@ -154,8 +211,8 @@ class RenderList {
|
|
|
"monthly_title_text_date_format".tr(),
|
|
|
"daily_title_text_date".tr(),
|
|
|
"daily_title_text_date_year".tr(),
|
|
|
- assetGroups,
|
|
|
- assetsPerRow,
|
|
|
+ assets,
|
|
|
+ layout,
|
|
|
),
|
|
|
);
|
|
|
}
|