import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart'; import '../../../../api/marianumcloud/talk/get_shared_items/get_shared_items.dart'; import '../../../../extensions/date_time.dart'; import '../../../../session/session_manager.dart'; import '../../../../share_intent/remote_file_ref.dart'; import '../../../../widget/media_gallery/gallery_item.dart'; import '../../../../widget/media_gallery/gallery_source.dart'; import '../details/shared_items_view.dart'; GalleryItem? galleryItemFromTalk(GetChatResponseObject message) { final file = message.messageParameters?['file']; if (file == null || file.path == null) return null; final kind = galleryMediaKindOf(file.name, mimeType: file.mimetype); if (kind == null) return null; return GalleryItem( file: RemoteFileRef.fromTalk(file), kind: kind, messageId: message.id, subtitle: '${message.actorDisplayName} ยท ${message.sentAt.formatDateTime()}', ); } /// All images and videos of a chat, via Talk's shared-items `media` stream. /// Opens on the tapped message and fills in around it in the background. class TalkGallerySource extends GallerySource { /// The share endpoint only pages backwards from the newest item, so reaching /// an old tapped image means walking down to it; give up beyond this. static const _maxCatchUpPages = 25; final String token; final GalleryItem initial; final int _epoch = SessionManager().sessionEpoch; List _items; int? _cursor; bool _hasOlder = false; bool _loading = false; bool _disposed = false; TalkGallerySource({required this.token, required this.initial}) : _items = [initial] { _catchUp(); } @override List get items => _items; @override bool get hasOlder => _hasOlder && !_loading; bool get _stale => _disposed || !SessionManager().isCurrentSession(_epoch); Future _fetch(int? cursor) async => buildSharedItemsPage( await GetSharedItems( token, objectType: 'media', limit: sharedItemsPageSize, lastKnownMessageId: cursor, ).run(), cursor, ); Iterable _toItems(SharedItemsPage page) => page.items.map(galleryItemFromTalk).nonNulls; Future _catchUp() async { final initialId = initial.messageId!; _loading = true; try { final collected = []; int? cursor; var hasMore = true; var reached = false; for (var pages = 0; hasMore && pages < _maxCatchUpPages; pages++) { final page = await _fetch(cursor); if (_stale) return; collected.addAll(_toItems(page)); cursor = page.lastKnownMessageId; hasMore = page.hasMore; if (page.items.any((item) => item.id <= initialId)) { reached = true; break; } } if (reached || !hasMore) { _items = mergeGalleryItems(_items, collected); _cursor = cursor; _hasOlder = hasMore; } else { // Too far down: skip the newer media and only page older ones. _cursor = initialId; _hasOlder = true; } } on Object { if (_stale) return; _cursor = initialId; _hasOlder = true; } finally { _loading = false; } notifyListeners(); } @override Future loadOlder() async { if (!hasOlder) return; _loading = true; try { final page = await _fetch(_cursor); if (_stale) return; _items = mergeGalleryItems(_items, _toItems(page)); _cursor = page.lastKnownMessageId; _hasOlder = page.hasMore; } on Object { // Retried on the next page change. } finally { _loading = false; } if (!_stale) notifyListeners(); } @override void dispose() { _disposed = true; super.dispose(); } }