fix: messages where opene even if the app is in the background

This commit is contained in:
otsmr 2026-04-27 19:50:28 +02:00
parent 5b5140ec7c
commit a93187c86d
3 changed files with 60 additions and 29 deletions

View file

@ -161,18 +161,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
}
}
Future<void> openedAllTextMessages(String groupId) {
final updates = MessagesCompanion(openedAt: Value(clock.now()));
return (update(messages)..where(
(t) =>
t.groupId.equals(groupId) &
t.senderId.isNotNull() &
t.openedAt.isNull() &
t.type.equals(MessageType.text.name),
))
.write(updates);
}
Future<void> handleMessageDeletion(
int? contactId,
String messageId,
@ -184,13 +172,13 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
return;
}
if (msg.mediaId != null && contactId != null) {
final otherMessagesWithSameMedia = await (select(messages)
..where(
(t) =>
t.mediaId.equals(msg.mediaId!) &
t.messageId.equals(messageId).not(),
))
.get();
final otherMessagesWithSameMedia =
await (select(messages)..where(
(t) =>
t.mediaId.equals(msg.mediaId!) &
t.messageId.equals(messageId).not(),
))
.get();
if (otherMessagesWithSameMedia.isEmpty) {
await (delete(
@ -210,7 +198,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
await (delete(
messageHistories,
)..where((t) => t.messageId.equals(messageId))).go();
await twonlyDB.receiptsDao.deleteReceiptsByMessageId(messageId);
await (update(messages)..where(

View file

@ -6,6 +6,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:mutex/mutex.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
@ -36,7 +37,7 @@ class ChatMessagesView extends StatefulWidget {
State<ChatMessagesView> createState() => _ChatMessagesViewState();
}
class _ChatMessagesViewState extends State<ChatMessagesView> {
class _ChatMessagesViewState extends State<ChatMessagesView> with WidgetsBindingObserver {
HashSet<int> alreadyReportedOpened = HashSet<int>();
late StreamSubscription<Group?> userSub;
late StreamSubscription<List<Message>> messageSub;
@ -64,6 +65,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
void initState() {
super.initState();
textFieldFocus = FocusNode();
WidgetsBinding.instance.addObserver(this);
initStreams();
}
@ -74,11 +76,26 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
contactSub?.cancel();
groupActionsSub?.cancel();
_nextTypingIndicator?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Mutex protectMessageUpdating = Mutex();
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
protectMessageUpdating.protect(() async {
await setMessages(allMessages, groupActions);
});
}
}
bool _isViewActive() {
return !AppState.isAppInBackground &&
(ModalRoute.of(context)?.isCurrent ?? false);
}
Future<void> initStreams() async {
final groupStream = twonlyDB.groupsDao.watchGroup(widget.groupId);
userSub = groupStream.listen((newGroup) {
@ -137,7 +154,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
List<Message> newMessages,
List<GroupHistory> groupActions,
) async {
await flutterLocalNotificationsPlugin.cancelAll();
if (_isViewActive()) {
await flutterLocalNotificationsPlugin.cancelAll();
}
final chatItems = <ChatItem>[];
final storedMediaFiles = <Message>[];
@ -189,11 +208,13 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
}
}
for (final contactId in openedMessages.keys) {
await notifyContactAboutOpeningMessage(
contactId,
openedMessages[contactId]!,
);
if (_isViewActive()) {
for (final contactId in openedMessages.keys) {
await notifyContactAboutOpeningMessage(
contactId,
openedMessages[contactId]!,
);
}
}
if (!mounted) return;

View file

@ -10,6 +10,7 @@ import 'package:lottie/lottie.dart';
import 'package:mutex/mutex.dart';
import 'package:no_screenshot/no_screenshot.dart';
import 'package:photo_view/photo_view.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
@ -46,7 +47,8 @@ class MediaViewerView extends StatefulWidget {
State<MediaViewerView> createState() => _MediaViewerViewState();
}
class _MediaViewerViewState extends State<MediaViewerView> {
class _MediaViewerViewState extends State<MediaViewerView>
with WidgetsBindingObserver {
Timer? nextMediaTimer;
Timer? progressTimer;
@ -87,6 +89,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
if (widget.initialMessage != null) {
allMediaFiles = [widget.initialMessage!];
}
WidgetsBinding.instance.addObserver(this);
asyncLoadNextMedia(true);
}
@ -101,11 +104,28 @@ class _MediaViewerViewState extends State<MediaViewerView> {
final tmp = videoController;
videoController = null;
tmp?.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
final Mutex _messageUpdateLock = Mutex();
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_messageUpdateLock.protect(() async {
if (currentMedia == null && allMediaFiles.isNotEmpty) {
await loadCurrentMediaFile();
}
});
}
}
bool _isViewActive() {
return !AppState.isAppInBackground &&
(ModalRoute.of(context)?.isCurrent ?? false);
}
Future<void> asyncLoadNextMedia(bool firstRun) async {
_subscription = twonlyDB.messagesDao
.watchMediaNotOpened(widget.group.groupId)
@ -195,7 +215,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
showSendTextMessageInput = false;
});
unawaited(flutterLocalNotificationsPlugin.cancelAll());
if (_isViewActive()) {
unawaited(flutterLocalNotificationsPlugin.cancelAll());
}
final stream = twonlyDB.mediaFilesDao.watchMedia(
allMediaFiles.first.mediaId!,