mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-07-18 01:14:07 +00:00
fix performance issue
This commit is contained in:
parent
9faa4fc49a
commit
7a8901cd2d
3 changed files with 103 additions and 51 deletions
|
|
@ -1,12 +1,13 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.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/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'mediafiles.dao.g.dart';
|
part 'mediafiles.dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [MediaFiles])
|
@DriftAccessor(tables: [MediaFiles, Messages])
|
||||||
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
with _$MediaFilesDaoMixin {
|
with _$MediaFilesDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
|
|
@ -142,7 +143,9 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
final query =
|
final query =
|
||||||
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
|
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
|
||||||
..groupBy([
|
..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();
|
return query.map((row) => row.readTable(mediaFiles)).watch();
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +157,17 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
.watch();
|
.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 {
|
Future<void> updateAllRetransmissionUploadingState() async {
|
||||||
await (update(mediaFiles)..where(
|
await (update(mediaFiles)..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ part of 'mediafiles.dao.dart';
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$MediaFilesDaoMixin on DatabaseAccessor<TwonlyDB> {
|
mixin _$MediaFilesDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
$MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles;
|
$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);
|
MediaFilesDaoManager get managers => MediaFilesDaoManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,4 +16,10 @@ class MediaFilesDaoManager {
|
||||||
MediaFilesDaoManager(this._db);
|
MediaFilesDaoManager(this._db);
|
||||||
$$MediaFilesTableTableManager get mediaFiles =>
|
$$MediaFilesTableTableManager get mediaFiles =>
|
||||||
$$MediaFilesTableTableManager(_db.attachedDatabase, _db.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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
|
@ -70,36 +70,38 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_lastMessageStream = lastMsgStream.listen((update) {
|
_lastMessageStream = lastMsgStream.listen((update) {
|
||||||
protectUpdateState.protect(() async {
|
_updateState(update, _messagesNotOpened);
|
||||||
await updateState(update, _messagesNotOpened);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_lastReactionStream = twonlyDB.reactionsDao.watchLastReactions(widget.group.groupId).listen((update) {
|
_lastReactionStream = twonlyDB.reactionsDao
|
||||||
if (!mounted) return;
|
.watchLastReactions(widget.group.groupId)
|
||||||
setState(() {
|
.listen((update) {
|
||||||
_lastReaction = update;
|
if (!mounted) return;
|
||||||
});
|
setState(() {
|
||||||
});
|
_lastReaction = update;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
_messagesNotOpenedStream = twonlyDB.messagesDao.watchMessageNotOpened(widget.group.groupId).listen((update) {
|
_messagesNotOpenedStream = twonlyDB.messagesDao
|
||||||
protectUpdateState.protect(() async {
|
.watchMessageNotOpened(widget.group.groupId)
|
||||||
await updateState(_lastMessage, update);
|
.listen((update) {
|
||||||
});
|
_updateState(_lastMessage, update);
|
||||||
});
|
});
|
||||||
|
|
||||||
_lastMediaFilesStream = twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) {
|
_lastMediaFilesStream = twonlyDB.mediaFilesDao
|
||||||
if (!mounted) return;
|
.watchMediaFilesForGroup(widget.group.groupId)
|
||||||
for (final mediaFile in mediaFiles) {
|
.listen((mediaFiles) {
|
||||||
final index = _previewMediaFiles.indexWhere(
|
if (!mounted) return;
|
||||||
(t) => t.mediaId == mediaFile.mediaId,
|
for (final mediaFile in mediaFiles) {
|
||||||
);
|
final index = _previewMediaFiles.indexWhere(
|
||||||
if (index >= 0) {
|
(t) => t.mediaId == mediaFile.mediaId,
|
||||||
_previewMediaFiles[index] = mediaFile;
|
);
|
||||||
}
|
if (index >= 0) {
|
||||||
}
|
_previewMediaFiles[index] = mediaFile;
|
||||||
setState(() {});
|
}
|
||||||
});
|
}
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
final groupContacts = await twonlyDB.groupsDao.getGroupContact(
|
final groupContacts = await twonlyDB.groupsDao.getGroupContact(
|
||||||
widget.group.groupId,
|
widget.group.groupId,
|
||||||
|
|
@ -110,19 +112,21 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex protectUpdateState = Mutex();
|
void _updateState(
|
||||||
|
|
||||||
Future<void> updateState(
|
|
||||||
Message? newLastMessage,
|
Message? newLastMessage,
|
||||||
List<Message> newMessagesNotOpened,
|
List<Message> newMessagesNotOpened,
|
||||||
) async {
|
) {
|
||||||
|
if (!mounted) return;
|
||||||
if (newLastMessage == null) {
|
if (newLastMessage == null) {
|
||||||
// there are no messages at all
|
// there are no messages at all
|
||||||
_currentMessage = null;
|
_currentMessage = null;
|
||||||
_previewMessages = [];
|
_previewMessages = [];
|
||||||
} else if (newMessagesNotOpened.isNotEmpty) {
|
} else if (newMessagesNotOpened.isNotEmpty) {
|
||||||
// Filter for the preview non opened messages. First messages which where send but not yet opened by the other side.
|
// Filter for the preview non opened messages. First messages which where
|
||||||
final receivedMessages = newMessagesNotOpened.where((x) => x.senderId != null).toList();
|
// send but not yet opened by the other side.
|
||||||
|
final receivedMessages = newMessagesNotOpened
|
||||||
|
.where((x) => x.senderId != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (receivedMessages.isNotEmpty) {
|
if (receivedMessages.isNotEmpty) {
|
||||||
_previewMessages = receivedMessages;
|
_previewMessages = receivedMessages;
|
||||||
|
|
@ -133,8 +137,9 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// there are no not opened messages show just the last message in the table
|
// 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
|
// only shows the last message in case there was no newer messages which
|
||||||
// This prevents, that it will show that a images got stored 10 days ago...
|
// already got deleted. This prevents showing that an image got stored 10
|
||||||
|
// days ago...
|
||||||
if (newLastMessage.createdAt.isAfter(
|
if (newLastMessage.createdAt.isAfter(
|
||||||
widget.group.lastMessageExchange.subtract(const Duration(days: 2)),
|
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 &&
|
if (msgs.isNotEmpty &&
|
||||||
msgs.first.type == MessageType.media.name &&
|
msgs.first.type == MessageType.media.name &&
|
||||||
!msgs.first.isDeletedFromSender &&
|
!msgs.first.isDeletedFromSender &&
|
||||||
|
|
@ -157,20 +164,30 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
_hasNonOpenedMediaFile = false;
|
_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) {
|
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(
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
message.mediaId!,
|
message.mediaId!,
|
||||||
);
|
);
|
||||||
if (mediaFile != null) {
|
if (mediaFile != null && mounted) {
|
||||||
_previewMediaFiles.add(mediaFile);
|
setState(() {
|
||||||
|
_previewMediaFiles.add(mediaFile);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastMessage = newLastMessage;
|
|
||||||
_messagesNotOpened = newMessagesNotOpened;
|
|
||||||
if (mounted) setState(() {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onTap() async {
|
Future<void> onTap() async {
|
||||||
|
|
@ -183,7 +200,9 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hasNonOpenedMediaFile) {
|
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(
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
msgs.first.mediaId!,
|
msgs.first.mediaId!,
|
||||||
);
|
);
|
||||||
|
|
@ -223,7 +242,10 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
VerificationBadgeComp(
|
VerificationBadgeComp(
|
||||||
group: widget.group,
|
group: widget.group,
|
||||||
showOnlyIfVerified: userService.currentUser.securityProfile.showOnlyVerifiedInChatViewList,
|
showOnlyIfVerified: userService
|
||||||
|
.currentUser
|
||||||
|
.securityProfile
|
||||||
|
.showOnlyVerifiedInChatViewList,
|
||||||
clickable: false,
|
clickable: false,
|
||||||
size: 12,
|
size: 12,
|
||||||
),
|
),
|
||||||
|
|
@ -258,7 +280,8 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
),
|
),
|
||||||
const Text('•'),
|
const Text('•'),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
if (_currentMessage != null) LastMessageTimeComp(message: _currentMessage),
|
if (_currentMessage != null)
|
||||||
|
LastMessageTimeComp(message: _currentMessage),
|
||||||
FlameCounterWidget(
|
FlameCounterWidget(
|
||||||
groupId: widget.group.groupId,
|
groupId: widget.group.groupId,
|
||||||
prefix: true,
|
prefix: true,
|
||||||
|
|
@ -272,7 +295,9 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
widget.group.groupId,
|
widget.group.groupId,
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await context.push(Routes.profileContact(contacts.first.userId));
|
await context.push(
|
||||||
|
Routes.profileContact(contacts.first.userId),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await context.push(Routes.profileGroup(widget.group.groupId));
|
await context.push(Routes.profileGroup(widget.group.groupId));
|
||||||
|
|
@ -285,7 +310,9 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
: IconButton(
|
: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_hasNonOpenedMediaFile) {
|
if (_hasNonOpenedMediaFile) {
|
||||||
context.push(Routes.chatsMessages(widget.group.groupId));
|
context.push(
|
||||||
|
Routes.chatsMessages(widget.group.groupId),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
context.push(
|
context.push(
|
||||||
Routes.chatsCameraSendTo,
|
Routes.chatsCameraSendTo,
|
||||||
|
|
@ -294,7 +321,9 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: FaIcon(
|
icon: FaIcon(
|
||||||
_hasNonOpenedMediaFile ? FontAwesomeIcons.solidComments : FontAwesomeIcons.camera,
|
_hasNonOpenedMediaFile
|
||||||
|
? FontAwesomeIcons.solidComments
|
||||||
|
: FontAwesomeIcons.camera,
|
||||||
color: context.color.outline.withAlpha(150),
|
color: context.color.outline.withAlpha(150),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue