80 lines
2.3 KiB
Dart
80 lines
2.3 KiB
Dart
import '../../share_intent/remote_file_ref.dart';
|
|
import '../file_viewer/file_kind.dart';
|
|
|
|
enum GalleryMediaKind { image, video }
|
|
|
|
/// Single rule for "is this a picture/video": the gallery and the chat bubble
|
|
/// both use it, so whatever looks like media in a chat is also in the gallery.
|
|
GalleryMediaKind? galleryMediaKindOf(String name, {String? mimeType}) {
|
|
final mime = mimeType?.toLowerCase() ?? '';
|
|
// Only formats the device decodes itself — image/* also covers PSD, SVG,
|
|
// TIFF …, which would hinge on a server preview.
|
|
if (_galleryImageMimes.contains(mime)) return GalleryMediaKind.image;
|
|
if (mime.startsWith('video/')) return GalleryMediaKind.video;
|
|
if (isGalleryImageName(name)) return GalleryMediaKind.image;
|
|
if (isGalleryVideoName(name)) return GalleryMediaKind.video;
|
|
return null;
|
|
}
|
|
|
|
const Set<String> _galleryImageMimes = {
|
|
'image/jpeg',
|
|
'image/png',
|
|
'image/gif',
|
|
'image/webp',
|
|
'image/bmp',
|
|
'image/x-ms-bmp',
|
|
'image/heic',
|
|
'image/heif',
|
|
'image/avif',
|
|
'image/x-icon',
|
|
'image/vnd.microsoft.icon',
|
|
};
|
|
|
|
class GalleryItem {
|
|
final RemoteFileRef file;
|
|
final GalleryMediaKind kind;
|
|
|
|
/// Talk message id: orders chat media and dedupes overlapping pages.
|
|
final int? messageId;
|
|
|
|
/// Second line in the app bar, e.g. sender and date.
|
|
final String? subtitle;
|
|
|
|
/// Part of the cache key so an overwritten file is not served stale.
|
|
final String? eTag;
|
|
|
|
const GalleryItem({
|
|
required this.file,
|
|
required this.kind,
|
|
this.messageId,
|
|
this.subtitle,
|
|
this.eTag,
|
|
});
|
|
|
|
String get key => messageId?.toString() ?? file.path;
|
|
|
|
bool get isVideo => kind == GalleryMediaKind.video;
|
|
|
|
String get _extension => file.name.split('.').last.toLowerCase();
|
|
|
|
/// Server previews of a GIF are a single still frame.
|
|
bool get needsOriginal => _extension == 'gif';
|
|
}
|
|
|
|
/// Adds [incoming] to [current], dropping duplicates by [GalleryItem.key] and
|
|
/// keeping chat media in chronological (message id) order.
|
|
List<GalleryItem> mergeGalleryItems(
|
|
List<GalleryItem> current,
|
|
Iterable<GalleryItem> incoming,
|
|
) {
|
|
final byKey = {for (final item in current) item.key: item};
|
|
for (final item in incoming) {
|
|
byKey.putIfAbsent(item.key, () => item);
|
|
}
|
|
final merged = byKey.values.toList();
|
|
if (merged.every((item) => item.messageId != null)) {
|
|
merged.sort((a, b) => a.messageId!.compareTo(b.messageId!));
|
|
}
|
|
return merged;
|
|
}
|