Aves_mio0.88/lib/widgets/activity/activity_data_loader.dart
2026-10-03 12:51:15 +02:00

217 lines
6.3 KiB
Dart

// lib/widgets/activity/activity_data_loader.dart
import 'package:aves/model/activity/activity_entry.dart';
import 'package:aves/model/activity/activity_grouping.dart';
import 'package:aves/model/activity/activity_preview_point.dart';
import 'package:aves/model/db/folder_content_type.dart';
import 'package:aves/model/db/folder_stats.dart';
import 'package:aves/services/common/services.dart';
class ActivityEntriesLoadResult {
final List<ActivityEntry> entries;
final Map<int, List<ActivityPreviewPoint>> previewPointsByActivityId;
const ActivityEntriesLoadResult({
required this.entries,
required this.previewPointsByActivityId,
});
}
class ActivityGroupsLoadResult<T> {
final List<T> groups;
final Map<int, List<ActivityPreviewPoint>> previewPointsByActivityId;
const ActivityGroupsLoadResult({
required this.groups,
required this.previewPointsByActivityId,
});
}
class ActivityDataLoader {
const ActivityDataLoader._();
static Future<ActivityEntriesLoadResult> loadEntries({
required bool favouriteOnly,
required bool withoutSportOnly,
String? sport,
String? directory,
}) async {
final loadedActivities = await localMediaDb.loadActivities(
sport: withoutSportOnly ? null : sport,
directory: directory,
favourite: favouriteOnly ? true : null,
);
Iterable<ActivityEntry> activities = loadedActivities;
if (withoutSportOnly) {
activities = activities.where((entry) {
final sport = entry.sport?.trim();
return sport == null || sport.isEmpty;
});
}
final entries = activities.toList();
final ids = entries.map((entry) => entry.id).whereType<int>().toSet();
final previewPoints = await localMediaDb.loadActivityPreviewPointsByActivityIds(ids);
return ActivityEntriesLoadResult(
entries: entries,
previewPointsByActivityId: previewPoints,
);
}
static Future<ActivityGroupsLoadResult<ActivitySportGroup>> loadSportGroups() async {
final activities = await localMediaDb.loadActivities();
final groups = ActivityGrouping.buildSportGroups(activities);
final ids = groups.map((group) => group.coverEntry?.id).whereType<int>().toSet();
final previewPoints = await localMediaDb.loadActivityPreviewPointsByActivityIds(ids);
return ActivityGroupsLoadResult<ActivitySportGroup>(
groups: groups,
previewPointsByActivityId: previewPoints,
);
}
static Future<ActivityGroupsLoadResult<ActivityAlbumGroup>> loadAlbumGroups() async {
final folders = await localMediaDb.loadFolders(
contentType: FolderContentType.activity,
);
final activities = await localMediaDb.loadActivities();
// Fallback utile finché non hai fatto una nuova scansione dopo
// l'introduzione di folders.content_type = activity.
if (folders.isEmpty) {
final groups = ActivityGrouping.buildAlbumGroups(activities);
final ids = groups.map((group) => group.coverEntry?.id).whereType<int>().toSet();
final previewPoints = await localMediaDb.loadActivityPreviewPointsByActivityIds(ids);
return ActivityGroupsLoadResult<ActivityAlbumGroup>(
groups: groups,
previewPointsByActivityId: previewPoints,
);
}
final activitiesById = <int, ActivityEntry>{};
for (final entry in activities) {
final id = entry.id;
if (id != null) {
activitiesById[id] = entry;
}
}
final groups =
folders.where((folder) => folder.hidden == 0).map((folder) {
final coverEntry = _coverEntryForFolder(
folder: folder,
activities: activities,
activitiesById: activitiesById,
);
return ActivityAlbumGroup(
displayName: _folderDisplayName(folder),
directoryFilter: folder.path,
count: folder.fileCount,
coverEntry: coverEntry,
);
}).toList()..sort(
(a, b) => a.displayName.toLowerCase().compareTo(
b.displayName.toLowerCase(),
),
);
final ids = groups.map((group) => group.coverEntry?.id).whereType<int>().toSet();
final previewPoints = await localMediaDb.loadActivityPreviewPointsByActivityIds(ids);
return ActivityGroupsLoadResult<ActivityAlbumGroup>(
groups: groups,
previewPointsByActivityId: previewPoints,
);
}
static ActivityEntry? _coverEntryForFolder({
required FolderStats folder,
required Iterable<ActivityEntry> activities,
required Map<int, ActivityEntry> activitiesById,
}) {
final thumbEntryId = folder.thumbEntryId;
if (thumbEntryId != null) {
final entry = activitiesById[thumbEntryId];
if (entry != null) return entry;
}
final normalizedFolderPath = _normalizePath(folder.path);
final matching =
activities.where((entry) {
return _folderPathForEntry(entry) == normalizedFolderPath;
}).toList()..sort((a, b) {
final aDate = a.startTimeMillis ?? a.dateModifiedMillis;
final bDate = b.startTimeMillis ?? b.dateModifiedMillis;
return bDate.compareTo(aDate);
});
if (matching.isEmpty) return null;
return matching.first;
}
static String _folderDisplayName(FolderStats folder) {
final displayName = folder.displayName.trim();
if (displayName.isNotEmpty) {
return displayName;
}
return _displayNameForPath(folder.path);
}
static String _folderPathForEntry(ActivityEntry entry) {
final relativePath = entry.relativePath?.trim();
if (relativePath != null && relativePath.isNotEmpty) {
return _normalizePath(relativePath);
}
final directoryName = entry.directoryName?.trim();
if (directoryName != null && directoryName.isNotEmpty) {
return _normalizePath(directoryName);
}
return 'Activity';
}
static String _normalizePath(String path) {
var normalized = path.replaceAll('\\', '/').trim();
while (normalized.contains('//')) {
normalized = normalized.replaceAll('//', '/');
}
if (normalized.endsWith('/')) {
normalized = normalized.substring(0, normalized.length - 1);
}
return normalized.isEmpty ? 'Activity' : normalized;
}
static String _displayNameForPath(String path) {
final normalized = _normalizePath(path);
final parts = normalized.split('/').where((part) => part.isNotEmpty);
if (parts.isEmpty) return 'Activity';
return parts.last;
}
}