This commit is contained in:
otsmr 2025-03-31 22:58:58 +02:00
parent bf34920350
commit cb595004e4
5 changed files with 115 additions and 79 deletions

View file

@ -144,6 +144,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
return (delete(messages)..where((t) => t.messageId.equals(messageId))).go();
}
Future deleteMessagesByContactId(int contactId) {
return (delete(messages)..where((t) => t.contactId.equals(contactId))).go();
}
Future<bool> containsOtherMessageId(
int fromUserId, int messageOtherId) async {
final query = select(messages)

View file

@ -83,6 +83,8 @@
"contactNickname": "Spitzname",
"contactNicknameNew": "Neuer Spitzname",
"contactBlock": "Blockieren",
"deleteAllContactMessages": "Alle Nachrichten löschen",
"deleteAllContactMessagesBody": "Dadurch werden alle Nachrichten in deinem Chat mit {username} gelöscht. Dies löscht NICHT die auf dem Gerät von {username} gespeicherten Nachrichten!",
"contactBlockTitle": "Blockiere {username}",
"contactBlockBody": "Ein blockierter Benutzer kann dir keine Nachrichten mehr senden, und sein Profil ist nicht mehr sichtbar. Um die Blockierung eines Benutzers aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Benutzer.",
"undo": "Rückgängig",

View file

@ -121,6 +121,8 @@
"contactVerifyNumberLongDesc": "To verify the end-to-end encryption with {username}, compare the numbers with their device. The person can also scan your code with their device.",
"contactNickname": "Nickname",
"contactNicknameNew": "New nickname",
"deleteAllContactMessages": "Delete all messages",
"deleteAllContactMessagesBody": "This will remove all messages in your chat with {username}. This will NOT delete the messages stored at {username}s device!",
"contactBlock": "Block",
"contactBlockTitle": "Block {username}",
"contactBlockBody": "A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.",

View file

@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
@ -121,6 +120,7 @@ class _ChatListViewState extends State<ChatListView> {
return RefreshIndicator(
onRefresh: () async {
await apiProvider.close(() {});
await apiProvider.connect();
await Future.delayed(Duration(seconds: 1));
},
@ -130,6 +130,7 @@ class _ChatListViewState extends State<ChatListView> {
itemBuilder: (BuildContext context, int index) {
final user = contacts[index];
return UserListItem(
key: ValueKey(user.userId),
user: user,
maxTotalMediaCounter: maxTotalMediaCounter,
);
@ -158,14 +159,80 @@ class _UserListItem extends State<UserListItem> {
MessageSendState state = MessageSendState.send;
Message? currentMessage;
List<Message> messagesNotOpened = [];
late StreamSubscription<List<Message>> messagesNotOpenedStream;
List<Message> lastMessages = [];
late StreamSubscription<List<Message>> lastMessageStream;
Timer? updateTime;
@override
void initState() {
super.initState();
initStreams();
lastUpdateTime();
}
void initStreams() {
lastMessageStream = twonlyDatabase.messagesDao
.watchLastMessage(widget.user.userId)
.listen((update) {
updateState(update, messagesNotOpened);
});
messagesNotOpenedStream = twonlyDatabase.messagesDao
.watchMessageNotOpened(widget.user.userId)
.listen((update) {
updateState(lastMessages, update);
});
}
void updateState(
List<Message> newLastMessages,
List<Message> newMessagesNotOpened,
) {
if (newLastMessages.isEmpty) {
// there are no messages at all
currentMessage = null;
} else if (newMessagesNotOpened.isEmpty) {
// there are no not opened messages show just the last message in the table
currentMessage = newLastMessages.first;
} else {
// filter first for received messages
final receivedMessages =
newMessagesNotOpened.where((x) => x.messageOtherId != null).toList();
if (receivedMessages.isNotEmpty) {
// There are received messages
final mediaMessages =
receivedMessages.where((x) => x.kind == MessageKind.media);
if (mediaMessages.isNotEmpty) {
currentMessage = mediaMessages.first;
} else {
currentMessage = receivedMessages.first;
}
} else {
// The not opened message was send
final mediaMessages =
newMessagesNotOpened.where((x) => x.kind == MessageKind.media);
if (mediaMessages.isNotEmpty) {
currentMessage = mediaMessages.first;
} else {
currentMessage = newMessagesNotOpened.first;
}
}
}
lastMessages = newLastMessages;
messagesNotOpened = newMessagesNotOpened;
setState(() {
// sets lastMessages, messagesNotOpened and currentMessage
});
}
void lastUpdateTime() {
// Change the color every 200 milliseconds
updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) {
@ -185,6 +252,8 @@ class _UserListItem extends State<UserListItem> {
void dispose() {
updateTime?.cancel();
super.dispose();
messagesNotOpenedStream.cancel();
lastMessageStream.cancel();
}
@override
@ -195,85 +264,26 @@ class _UserListItem extends State<UserListItem> {
contact: widget.user,
child: ListTile(
title: Text(getContactDisplayName(widget.user)),
subtitle: StreamBuilder(
stream:
twonlyDatabase.messagesDao.watchLastMessage(widget.user.userId),
builder: (context, lastMessageSnapshot) {
if (!lastMessageSnapshot.hasData) {
return Container();
}
if (lastMessageSnapshot.data!.isEmpty) {
return Text(context.lang.chatsTapToSend);
}
final lastMessage = lastMessageSnapshot.data!.first;
return StreamBuilder(
stream: twonlyDatabase.messagesDao
.watchMessageNotOpened(widget.user.userId),
builder: (context, notOpenedMessagesSnapshot) {
if (!lastMessageSnapshot.hasData) {
return Container();
}
var lastMessages = [lastMessage];
if (notOpenedMessagesSnapshot.data != null &&
notOpenedMessagesSnapshot.data!.isNotEmpty) {
// filter first for only received messages
var notOpenedMessages = notOpenedMessagesSnapshot.data!;
lastMessages = notOpenedMessages
.where((x) => x.messageOtherId != null)
.toList();
// For send images show only one
if (lastMessages.isEmpty) {
var media = notOpenedMessages
.where((x) => x.kind == MessageKind.media);
if (media.isNotEmpty) {
currentMessage = media.first;
lastMessages = [currentMessage!];
} else {
currentMessage = notOpenedMessages.first;
lastMessages = [currentMessage!];
}
} else {
// there are multiple messages received
var media =
lastMessages.where((x) => x.kind == MessageKind.media);
if (media.isNotEmpty) {
currentMessage = media.first;
} else {
currentMessage = lastMessages.first;
}
}
} else {
currentMessage = lastMessage;
}
return Row(
children: [
MessageSendStateIcon(lastMessages),
Text(""),
const SizedBox(width: 5),
Text(
formatDuration(lastMessageInSeconds),
style: TextStyle(fontSize: 12),
subtitle: (currentMessage == null)
? Text(context.lang.chatsTapToSend)
: Row(
children: [
MessageSendStateIcon(lastMessages),
Text(""),
const SizedBox(width: 5),
Text(
formatDuration(lastMessageInSeconds),
style: TextStyle(fontSize: 12),
),
if (flameCounter > 0)
FlameCounterWidget(
widget.user,
flameCounter,
widget.maxTotalMediaCounter,
prefix: true,
),
if (flameCounter > 0)
FlameCounterWidget(
widget.user,
flameCounter,
widget.maxTotalMediaCounter,
prefix: true,
),
],
);
},
);
},
),
],
),
leading: ContactAvatar(contact: widget.user),
onTap: () {
if (currentMessage == null) {

View file

@ -94,6 +94,24 @@ class _ContactViewState extends State<ContactView> {
));
},
),
BetterListTile(
icon: FontAwesomeIcons.trashCan,
text: context.lang.deleteAllContactMessages,
onTap: () async {
bool block = await showAlertDialog(
context,
context.lang.deleteAllContactMessages,
context.lang.deleteAllContactMessagesBody(
getContactDisplayName(contact)),
);
if (block) {
if (context.mounted) {
await twonlyDatabase.messagesDao
.deleteMessagesByContactId(contact.userId);
}
}
},
),
BetterListTile(
icon: FontAwesomeIcons.ban,
color: Colors.red,