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 _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 mergeGalleryItems( List current, Iterable 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; }