fix performance issue

This commit is contained in:
otsmr 2026-07-07 21:26:42 +02:00
parent 9faa4fc49a
commit 7a8901cd2d
3 changed files with 103 additions and 51 deletions

View file

@ -1,12 +1,13 @@
import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/log.dart';
part 'mediafiles.dao.g.dart';
@DriftAccessor(tables: [MediaFiles])
@DriftAccessor(tables: [MediaFiles, Messages])
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
with _$MediaFilesDaoMixin {
// this constructor is required so that the main database can create an instance
@ -142,7 +143,9 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
final query =
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
..groupBy([
const CustomExpression<Object>('COALESCE(stored_file_hash, media_id)')
const CustomExpression<Object>(
'COALESCE(stored_file_hash, media_id)',
),
]);
return query.map((row) => row.readTable(mediaFiles)).watch();
}
@ -154,6 +157,17 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
.watch();
}
Stream<List<MediaFile>> watchMediaFilesForGroup(String groupId) {
final query = select(mediaFiles).join([
innerJoin(
db.messages,
db.messages.mediaId.equalsExp(mediaFiles.mediaId),
useColumns: false,
),
])..where(db.messages.groupId.equals(groupId));
return query.map((row) => row.readTable(mediaFiles)).watch();
}
Future<void> updateAllRetransmissionUploadingState() async {
await (update(mediaFiles)..where(
(t) =>

View file

@ -5,6 +5,9 @@ part of 'mediafiles.dao.dart';
// ignore_for_file: type=lint
mixin _$MediaFilesDaoMixin on DatabaseAccessor<TwonlyDB> {
$MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles;
$GroupsTable get groups => attachedDatabase.groups;
$ContactsTable get contacts => attachedDatabase.contacts;
$MessagesTable get messages => attachedDatabase.messages;
MediaFilesDaoManager get managers => MediaFilesDaoManager(this);
}
@ -13,4 +16,10 @@ class MediaFilesDaoManager {
MediaFilesDaoManager(this._db);
$$MediaFilesTableTableManager get mediaFiles =>
$$MediaFilesTableTableManager(_db.attachedDatabase, _db.mediaFiles);
$$GroupsTableTableManager get groups =>
$$GroupsTableTableManager(_db.attachedDatabase, _db.groups);
$$ContactsTableTableManager get contacts =>
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
$$MessagesTableTableManager get messages =>
$$MessagesTableTableManager(_db.attachedDatabase, _db.messages);
}

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:mutex/mutex.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
@ -70,25 +70,27 @@ class _UserListItem extends State<GroupListItemComp> {
);
if (!mounted) return;
_lastMessageStream = lastMsgStream.listen((update) {
protectUpdateState.protect(() async {
await updateState(update, _messagesNotOpened);
});
_updateState(update, _messagesNotOpened);
});
_lastReactionStream = twonlyDB.reactionsDao.watchLastReactions(widget.group.groupId).listen((update) {
_lastReactionStream = twonlyDB.reactionsDao
.watchLastReactions(widget.group.groupId)
.listen((update) {
if (!mounted) return;
setState(() {
_lastReaction = update;
});
});
_messagesNotOpenedStream = twonlyDB.messagesDao.watchMessageNotOpened(widget.group.groupId).listen((update) {
protectUpdateState.protect(() async {
await updateState(_lastMessage, update);
});
_messagesNotOpenedStream = twonlyDB.messagesDao
.watchMessageNotOpened(widget.group.groupId)
.listen((update) {
_updateState(_lastMessage, update);
});
_lastMediaFilesStream = twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) {
_lastMediaFilesStream = twonlyDB.mediaFilesDao
.watchMediaFilesForGroup(widget.group.groupId)
.listen((mediaFiles) {
if (!mounted) return;
for (final mediaFile in mediaFiles) {
final index = _previewMediaFiles.indexWhere(
@ -110,19 +112,21 @@ class _UserListItem extends State<GroupListItemComp> {
}
}
Mutex protectUpdateState = Mutex();
Future<void> updateState(
void _updateState(
Message? newLastMessage,
List<Message> newMessagesNotOpened,
) async {
) {
if (!mounted) return;
if (newLastMessage == null) {
// there are no messages at all
_currentMessage = null;
_previewMessages = [];
} else if (newMessagesNotOpened.isNotEmpty) {
// Filter for the preview non opened messages. First messages which where send but not yet opened by the other side.
final receivedMessages = newMessagesNotOpened.where((x) => x.senderId != null).toList();
// Filter for the preview non opened messages. First messages which where
// send but not yet opened by the other side.
final receivedMessages = newMessagesNotOpened
.where((x) => x.senderId != null)
.toList();
if (receivedMessages.isNotEmpty) {
_previewMessages = receivedMessages;
@ -133,8 +137,9 @@ class _UserListItem extends State<GroupListItemComp> {
}
} else {
// there are no not opened messages show just the last message in the table
// only shows the last message in case there was no newer messages which already got deleted
// This prevents, that it will show that a images got stored 10 days ago...
// only shows the last message in case there was no newer messages which
// already got deleted. This prevents showing that an image got stored 10
// days ago...
if (newLastMessage.createdAt.isAfter(
widget.group.lastMessageExchange.subtract(const Duration(days: 2)),
)) {
@ -146,7 +151,9 @@ class _UserListItem extends State<GroupListItemComp> {
}
}
final msgs = _previewMessages.where((x) => x.type == MessageType.media.name).toList();
final msgs = _previewMessages
.where((x) => x.type == MessageType.media.name)
.toList();
if (msgs.isNotEmpty &&
msgs.first.type == MessageType.media.name &&
!msgs.first.isDeletedFromSender &&
@ -157,20 +164,30 @@ class _UserListItem extends State<GroupListItemComp> {
_hasNonOpenedMediaFile = false;
}
_lastMessage = newLastMessage;
_messagesNotOpened = newMessagesNotOpened;
setState(() {});
// Only fetch on first load when a mediaId is not yet cached.
_fetchMissingMediaFiles();
}
/// Fetches any media files referenced by preview messages but not yet in the
/// local cache. Fire-and-forget; updates state when results arrive.
Future<void> _fetchMissingMediaFiles() async {
for (final message in _previewMessages) {
if (message.mediaId != null && !_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) {
if (message.mediaId != null &&
!_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) {
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
message.mediaId!,
);
if (mediaFile != null) {
if (mediaFile != null && mounted) {
setState(() {
_previewMediaFiles.add(mediaFile);
});
}
}
}
_lastMessage = newLastMessage;
_messagesNotOpened = newMessagesNotOpened;
if (mounted) setState(() {});
}
Future<void> onTap() async {
@ -183,7 +200,9 @@ class _UserListItem extends State<GroupListItemComp> {
}
if (_hasNonOpenedMediaFile) {
final msgs = _previewMessages.where((x) => x.type == MessageType.media.name).toList();
final msgs = _previewMessages
.where((x) => x.type == MessageType.media.name)
.toList();
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
msgs.first.mediaId!,
);
@ -223,7 +242,10 @@ class _UserListItem extends State<GroupListItemComp> {
const SizedBox(width: 3),
VerificationBadgeComp(
group: widget.group,
showOnlyIfVerified: userService.currentUser.securityProfile.showOnlyVerifiedInChatViewList,
showOnlyIfVerified: userService
.currentUser
.securityProfile
.showOnlyVerifiedInChatViewList,
clickable: false,
size: 12,
),
@ -258,7 +280,8 @@ class _UserListItem extends State<GroupListItemComp> {
),
const Text(''),
const SizedBox(width: 5),
if (_currentMessage != null) LastMessageTimeComp(message: _currentMessage),
if (_currentMessage != null)
LastMessageTimeComp(message: _currentMessage),
FlameCounterWidget(
groupId: widget.group.groupId,
prefix: true,
@ -272,7 +295,9 @@ class _UserListItem extends State<GroupListItemComp> {
widget.group.groupId,
);
if (!context.mounted) return;
await context.push(Routes.profileContact(contacts.first.userId));
await context.push(
Routes.profileContact(contacts.first.userId),
);
return;
} else {
await context.push(Routes.profileGroup(widget.group.groupId));
@ -285,7 +310,9 @@ class _UserListItem extends State<GroupListItemComp> {
: IconButton(
onPressed: () {
if (_hasNonOpenedMediaFile) {
context.push(Routes.chatsMessages(widget.group.groupId));
context.push(
Routes.chatsMessages(widget.group.groupId),
);
} else {
context.push(
Routes.chatsCameraSendTo,
@ -294,7 +321,9 @@ class _UserListItem extends State<GroupListItemComp> {
}
},
icon: FaIcon(
_hasNonOpenedMediaFile ? FontAwesomeIcons.solidComments : FontAwesomeIcons.camera,
_hasNonOpenedMediaFile
? FontAwesomeIcons.solidComments
: FontAwesomeIcons.camera,
color: context.color.outline.withAlpha(150),
),
),