diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 123d76ed..532ddd92 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -66,6 +66,25 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { return query.map((row) => row.readTable(messages)).watch(); } + Stream> watchUnopenedMediaFiles() { + final query = + select(messages).join([ + leftOuterJoin( + mediaFiles, + mediaFiles.mediaId.equalsExp(messages.mediaId), + ), + ]) + ..where( + messages.openedAt.isNull() & + messages.mediaId.isNotNull() & + messages.type.equals(MessageType.media.name) & + mediaFiles.downloadState.equals(DownloadState.ready.name) & + (mediaFiles.type.equals(MediaType.image.name) | + mediaFiles.type.equals(MediaType.gif.name)), + ); + return query.map((row) => row.readTable(mediaFiles)).watch(); + } + Future> watchLastMessage(String groupId) async { final group = await twonlyDB.groupsDao.getGroup(groupId); final deletionTime = clock.now().subtract( diff --git a/lib/src/visual/views/chats/chat_list.view.dart b/lib/src/visual/views/chats/chat_list.view.dart index 421d2a2e..e119d60d 100644 --- a/lib/src/visual/views/chats/chat_list.view.dart +++ b/lib/src/visual/views/chats/chat_list.view.dart @@ -11,8 +11,10 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; +import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/services/user.service.dart'; +import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/connection_status.comp.dart'; @@ -34,6 +36,8 @@ class _ChatListViewState extends State { StreamSubscription? _userSub; StreamSubscription>? _contactsSub; StreamSubscription>? _contactsCountSub; + StreamSubscription>? _precacheSub; + final Set _precachedMediaIds = {}; List _groupsNotPinned = []; List _groupsPinned = []; List _groupsArchived = []; @@ -105,6 +109,24 @@ class _ChatListViewState extends State { }); }); + _precacheSub = twonlyDB.messagesDao.watchUnopenedMediaFiles().listen((mediaFiles) { + if (!mounted) return; + for (final media in mediaFiles) { + if (!_precachedMediaIds.contains(media.mediaId)) { + _precachedMediaIds.add(media.mediaId); + final fileService = MediaFileService(media); + if (fileService.tempPath.existsSync()) { + precacheImage( + FileImage(fileService.tempPath), + context, + ).catchError((Object e, StackTrace st) { + Log.error('Failed to precache image in ChatListView: $e\n$st'); + }); + } + } + } + }); + WidgetsBinding.instance.addPostFrameCallback((_) async { final changeLog = await rootBundle.loadString('CHANGELOG.md'); final changeLogHash = (await compute( @@ -137,6 +159,7 @@ class _ChatListViewState extends State { _countContactRequestStream.cancel(); _countAnnouncedStream.cancel(); _userSub?.cancel(); + _precacheSub?.cancel(); super.dispose(); } diff --git a/lib/src/visual/views/chats/media_viewer.view.dart b/lib/src/visual/views/chats/media_viewer.view.dart index f8b1d150..6b6d4559 100644 --- a/lib/src/visual/views/chats/media_viewer.view.dart +++ b/lib/src/visual/views/chats/media_viewer.view.dart @@ -721,6 +721,7 @@ class _MediaViewerViewState extends State { imageProvider: FileImage( currentMedia!.tempPath, ), + loadingBuilder: (context, event) => _loader(), initialScale: PhotoViewComputedScale.contained, minScale: PhotoViewComputedScale.contained, errorBuilder: (context, error, stackTrace) {