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
@@ -1,12 +1,10 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart';
|
||||
import '../../../../api/marianumcloud/talk/chat/rich_object_string_processor.dart';
|
||||
import '../../../../model/endpoint_data.dart';
|
||||
import '../../../../session/session_manager.dart';
|
||||
import '../../../../utils/emoji_detection.dart';
|
||||
import '../../../../utils/url_opener.dart';
|
||||
import '../widgets/chat_file_attachment.dart';
|
||||
import '../widgets/highlighted_linkify.dart';
|
||||
import '../widgets/message_markdown.dart';
|
||||
|
||||
@@ -78,36 +76,7 @@ class ChatMessage {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
errorWidget: (context, url, error) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.file_open_outlined, size: 35),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(
|
||||
file.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
placeholder: (context, url) => const Padding(
|
||||
padding: EdgeInsets.all(15),
|
||||
child: SizedBox(width: 50, child: LinearProgressIndicator()),
|
||||
),
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
errorListener: (value) {},
|
||||
httpHeaders: SessionManager().requireNextcloud().authHeaders,
|
||||
imageUrl:
|
||||
'${EndpointData().nextcloud().origin()}/index.php/core/preview?fileId=${file.id}&x=130&y=-1&a=1',
|
||||
),
|
||||
ChatFileAttachment(file: file),
|
||||
if (originalMessage != '{file}') ...[
|
||||
SizedBox(height: 5),
|
||||
contentWidget,
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
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<GalleryItem> _items;
|
||||
int? _cursor;
|
||||
bool _hasOlder = false;
|
||||
bool _loading = false;
|
||||
bool _disposed = false;
|
||||
|
||||
TalkGallerySource({required this.token, required this.initial})
|
||||
: _items = [initial] {
|
||||
_catchUp();
|
||||
}
|
||||
|
||||
@override
|
||||
List<GalleryItem> get items => _items;
|
||||
|
||||
@override
|
||||
bool get hasOlder => _hasOlder && !_loading;
|
||||
|
||||
bool get _stale => _disposed || !SessionManager().isCurrentSession(_epoch);
|
||||
|
||||
Future<SharedItemsPage> _fetch(int? cursor) async => buildSharedItemsPage(
|
||||
await GetSharedItems(
|
||||
token,
|
||||
objectType: 'media',
|
||||
limit: sharedItemsPageSize,
|
||||
lastKnownMessageId: cursor,
|
||||
).run(),
|
||||
cursor,
|
||||
);
|
||||
|
||||
Iterable<GalleryItem> _toItems(SharedItemsPage page) =>
|
||||
page.items.map(galleryItemFromTalk).nonNulls;
|
||||
|
||||
Future<void> _catchUp() async {
|
||||
final initialId = initial.messageId!;
|
||||
_loading = true;
|
||||
try {
|
||||
final collected = <GalleryItem>[];
|
||||
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<void> 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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user