added a swipeable media gallery for chat and folder images and videos with server previews, on-demand originals and zoom, and marked document previews in chat so they no longer pass for photos
This commit is contained in:
1 parent
952f916231
commit
aa176c90e3
22 files changed
+1658
-103
No files matched your search
@@ -0,0 +1,79 @@
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user