fix(ui,shared,service): Use a UUID as a unique message key

This commit is contained in:
PapaTutuWawa 2023-08-12 18:58:24 +02:00
parent 865846af9a
commit 301ff664a8
27 changed files with 272 additions and 375 deletions

View File

@ -108,9 +108,7 @@ files:
implements:
- JsonImplementation
attributes:
key:
type: MessageKey
deserialise: true
id: String
progress: double?
# Triggered by [RosterService] if we receive a roster push.
- name: RosterDiffEvent
@ -559,26 +557,21 @@ files:
implements:
- JsonImplementation
attributes:
sid: String
conversationJid: String
id: String
sendMarker: bool
- name: AddReactionToMessageCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
key:
type: MessageKey
deserialise: true
id: String
emoji: String
- name: RemoveReactionFromMessageCommand
extends: BackgroundCommand
implements:
- JsonImplementation
attributes:
key:
type: MessageKey
deserialise: true
id: String
emoji: String
- name: MarkOmemoDeviceAsVerifiedCommand
extends: BackgroundCommand
@ -652,9 +645,7 @@ files:
implements:
- JsonImplementation
attributes:
key:
type: MessageKey
deserialise: true
id: String
- name: RequestAvatarForJidCommand
extends: BackgroundCommand
implements:

View File

@ -84,9 +84,8 @@ class ConversationService {
Message? lastMessage;
if (c['lastMessageId'] != null) {
lastMessage = await GetIt.I.get<MessageService>().getMessageBySid(
lastMessage = await GetIt.I.get<MessageService>().getMessageById(
c['lastMessageId']! as String,
jid,
accountJid,
queryReactionPreview: false,
);
@ -173,7 +172,7 @@ class ConversationService {
final c = <String, dynamic>{};
if (lastMessage != null) {
c['lastMessageId'] = lastMessage.sid;
c['lastMessageId'] = lastMessage.id;
}
if (lastChangeTimestamp != null) {
c['lastChangeTimestamp'] = lastChangeTimestamp;

View File

@ -2,6 +2,7 @@ import 'package:get_it/get_it.dart';
import 'package:moxxyv2/service/database/constants.dart';
import 'package:moxxyv2/service/xmpp_state.dart';
import 'package:sqflite_sqlcipher/sqflite.dart';
import 'package:uuid/uuid.dart';
extension MaybeGet<K, V> on Map<K, V> {
V? maybeGet(K? key) {
@ -65,6 +66,7 @@ Future<void> upgradeFromV45ToV46(Database db) async {
await db.execute(
'''
CREATE TABLE ${messagesTable}_new (
id TEXT PRIMARY KEY,
accountJid TEXT NOT NULL,
sender TEXT NOT NULL,
body TEXT,
@ -79,7 +81,7 @@ Future<void> upgradeFromV45ToV46(Database db) async {
displayed INTEGER,
acked INTEGER,
originId TEXT,
quote_sid TEXT,
quote_id TEXT,
file_metadata_id TEXT,
isDownloading INTEGER NOT NULL,
isUploading INTEGER NOT NULL,
@ -89,30 +91,24 @@ Future<void> upgradeFromV45ToV46(Database db) async {
stickerPackId TEXT,
pseudoMessageType INTEGER,
pseudoMessageData TEXT,
PRIMARY KEY (accountJid, sender, sid, timestamp, conversationJid),
CONSTRAINT fk_quote
FOREIGN KEY (accountJid, quote_sid)
REFERENCES $messagesTable (accountJid, sid)
FOREIGN KEY (accountJid, quote_id)
REFERENCES $messagesTable (accountJid, id)
CONSTRAINT fk_file_metadata
FOREIGN KEY (file_metadata_id)
REFERENCES $fileMetadataTable (id)
)''',
);
// Build up the message map
/// Message's old id attribute -> Message's sid attribute.
/// Message's old id attribute -> Message's new UUID attribute.
const uuid = Uuid();
final messageMap = <int, String>{};
final conversationMap = <int, String>{};
final timestampMap = <int, int>{};
final senderMap = <int, String>{};
if (migrateRows) {
final messages = await db.query(messagesTable);
for (final message in messages) {
messageMap[message['id']! as int] = message['sid']! as String;
conversationMap[message['id']! as int] =
message['conversationJid']! as String;
timestampMap[message['id']! as int] = message['timestamp']! as int;
senderMap[message['id']! as int] = message['sender']! as String;
messageMap[message['id']! as int] = uuid.v4();
}
// Then migrate messages
for (final message in messages) {
@ -121,7 +117,8 @@ Future<void> upgradeFromV45ToV46(Database db) async {
..remove('id')
..remove('quote_id'),
'accountJid': accountJid,
'quote_sid': messageMap.maybeGet(message['quote_id'] as int?)
'quote_id': messageMap.maybeGet(message['quote_id'] as int?),
'id': messageMap[message['id']! as int]!,
});
}
}
@ -156,7 +153,7 @@ Future<void> upgradeFromV45ToV46(Database db) async {
PRIMARY KEY (jid, accountJid),
CONSTRAINT fk_last_message
FOREIGN KEY (accountJid, lastMessageId)
REFERENCES $messagesTable (accountJid, sid),
REFERENCES $messagesTable (accountJid, id),
CONSTRAINT fk_contact_id
FOREIGN KEY (contactId)
REFERENCES $contactsTable (id)
@ -188,17 +185,14 @@ Future<void> upgradeFromV45ToV46(Database db) async {
await db.execute(
'''
CREATE TABLE ${reactionsTable}_new (
accountJid TEXT NOT NULL,
message_timestamp INTEGER NOT NULL,
message_sender TEXT NOT NULL,
senderJid TEXT NOT NULL,
emoji TEXT NOT NULL,
message_sid TEXT NOT NULL,
conversationJid TEXT NOT NULL,
PRIMARY KEY (accountJid, senderJid, emoji, message_sid, conversationJid, message_timestamp, message_sender),
accountJid TEXT NOT NULL,
message_id TEXT NOT NULL,
senderJid TEXT NOT NULL,
emoji TEXT NOT NULL,
PRIMARY KEY (accountJid, senderJid, emoji, message_id),
CONSTRAINT fk_message
FOREIGN KEY (accountJid, message_sid, conversationJid, message_timestamp, message_sender)
REFERENCES $messagesTable (accountJid, sid, conversationJid, timestamp, sender)
FOREIGN KEY (accountJid, message_id)
REFERENCES $messagesTable (accountJid, id)
ON DELETE CASCADE
)''',
);
@ -207,12 +201,10 @@ Future<void> upgradeFromV45ToV46(Database db) async {
await db.insert(
'${reactionsTable}_new',
{
...Map.from(reaction)..remove('message_id'),
'message_sid': messageMap.maybeGet(reaction['message_id']! as int),
'conversationJid': conversationMap[reaction['message_id']! as int],
...Map.from(reaction)
..remove('message_id'),
'message_id': messageMap.maybeGet(reaction['message_id']! as int),
'accountJid': accountJid,
'message_timestamp': timestampMap[reaction['message_id']! as int],
'message_sender': senderMap[reaction['message_id']! as int],
},
);
}

View File

@ -669,8 +669,7 @@ Future<void> performRequestDownload(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final message = await ms.updateMessage(
command.message.sid,
command.message.conversationJid,
command.message.id,
accountJid,
isDownloading: true,
);
@ -687,7 +686,9 @@ Future<void> performRequestDownload(
await srv.downloadFile(
FileDownloadJob(
message.messageKey,
message.id,
message.conversationJid,
accountJid,
MediaFileLocation(
fileMetadata.sourceUrls!,
fileMetadata.filename,
@ -702,7 +703,6 @@ Future<void> performRequestDownload(
fileMetadata.ciphertextHashes,
null,
),
accountJid,
message.fileMetadata!.id,
message.fileMetadata!.plaintextHashes?.isNotEmpty ?? false,
mimeGuess,
@ -1056,8 +1056,7 @@ Future<void> performMarkConversationAsRead(
if (conversation.lastMessage != null) {
await GetIt.I.get<MessageService>().markMessageAsRead(
conversation.lastMessage!.sid,
conversation.lastMessage!.conversationJid,
conversation.lastMessage!.id,
accountJid,
conversation.type != ConversationType.note,
);
@ -1077,8 +1076,7 @@ Future<void> performMarkMessageAsRead(
}) async {
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
await GetIt.I.get<MessageService>().markMessageAsRead(
command.sid,
command.conversationJid,
command.id,
accountJid,
command.sendMarker,
);
@ -1091,8 +1089,7 @@ Future<void> performAddMessageReaction(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final rs = GetIt.I.get<ReactionsService>();
final msg = await rs.addNewReaction(
command.key.sid,
command.key.conversationJid,
command.id,
accountJid,
accountJid,
command.emoji,
@ -1108,19 +1105,18 @@ Future<void> performAddMessageReaction(
),
);
if (command.key.conversationJid != '') {
if (msg.conversationJid != '') {
// Send the reaction
final manager = GetIt.I
.get<XmppConnection>()
.getManagerById<MessageManager>(messageManager)!;
await manager.sendMessage(
JID.fromString(command.key.conversationJid),
JID.fromString(msg.conversationJid),
TypedMap<StanzaHandlerExtension>.fromList([
MessageReactionsData(
msg.originId ?? msg.sid,
await rs.getReactionsForMessageByJid(
command.key.sid,
command.key.conversationJid,
command.id,
accountJid,
accountJid,
),
@ -1140,8 +1136,7 @@ Future<void> performRemoveMessageReaction(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final rs = GetIt.I.get<ReactionsService>();
final msg = await rs.removeReaction(
command.key.sid,
command.key.conversationJid,
command.id,
accountJid,
accountJid,
command.emoji,
@ -1158,19 +1153,18 @@ Future<void> performRemoveMessageReaction(
);
if (command.key.conversationJid != '') {
if (msg.conversationJid != '') {
// Send the reaction
final manager = GetIt.I
.get<XmppConnection>()
.getManagerById<MessageManager>(messageManager)!;
await manager.sendMessage(
JID.fromString(command.key.conversationJid),
JID.fromString(msg.conversationJid),
TypedMap<StanzaHandlerExtension>.fromList([
MessageReactionsData(
msg.originId ?? msg.sid,
await rs.getReactionsForMessageByJid(
command.key.sid,
command.key.conversationJid,
command.id,
accountJid,
accountJid,
),
@ -1395,8 +1389,7 @@ Future<void> performGetReactions(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final reactionsRaw =
await GetIt.I.get<ReactionsService>().getReactionsForMessage(
command.key.sid,
command.key.conversationJid,
command.id,
accountJid,
);
final reactionsMap = <String, List<String>>{};

View File

@ -144,8 +144,7 @@ class HttpFileTransferService {
for (final recipient in job.recipients) {
final m = job.messageMap[recipient]!;
final msg = await ms.updateMessage(
m.sid,
m.conversationJid,
m.id,
job.accountJid,
errorType: error,
isUploading: false,
@ -155,7 +154,7 @@ class HttpFileTransferService {
// Update the conversation list
final conversation =
await cs.getConversationByJid(recipient, job.accountJid);
if (conversation?.lastMessage?.sid == msg.sid) {
if (conversation?.lastMessage?.id == msg.id) {
final newConversation = conversation!.copyWith(
lastMessage: msg,
);
@ -217,7 +216,6 @@ class HttpFileTransferService {
}
final slot = slotResult.get<HttpFileUploadSlot>();
final messageKey = job.messageMap.values.first.messageKey;
final uploadStatusCode = await client.uploadFile(
Uri.parse(slot.putUrl),
slot.headers,
@ -229,7 +227,7 @@ class HttpFileTransferService {
final progress = current.toDouble() / total.toDouble();
sendEvent(
ProgressEvent(
key: messageKey,
id: job.messageMap[job.recipients.first]!.id,
progress: progress == 1 ? 0.99 : progress,
),
);
@ -328,8 +326,7 @@ class HttpFileTransferService {
// Notify UI of upload completion
final m = job.messageMap[recipient]!;
var msg = await ms.updateMessage(
m.sid,
m.conversationJid,
m.id,
job.accountJid,
errorType: null,
isUploading: false,
@ -338,10 +335,9 @@ class HttpFileTransferService {
// TODO(Unknown): Maybe batch those two together?
final oldSid = msg.sid;
msg = await ms.updateMessage(
msg.sid,
msg.conversationJid,
msg.id,
job.accountJid,
newSid: uuid.v4(),
sid: uuid.v4(),
originId: uuid.v4(),
);
sendEvent(MessageUpdatedEvent(message: msg));
@ -407,8 +403,7 @@ class HttpFileTransferService {
// Notify UI of download failure
final msg = await ms.updateMessage(
job.messageKey.sid,
job.messageKey.conversationJid,
job.messageId,
job.accountJid,
errorType: error,
isDownloading: false,
@ -450,7 +445,7 @@ class HttpFileTransferService {
final progress = current.toDouble() / total.toDouble();
sendEvent(
ProgressEvent(
key: job.messageKey,
id: job.messageId,
progress: progress == 1 ? 0.99 : progress,
),
);
@ -476,7 +471,7 @@ class HttpFileTransferService {
// The file was downloaded and is now being decrypted
sendEvent(
ProgressEvent(
key: job.messageKey,
id: job.messageId,
),
);
@ -590,7 +585,7 @@ class HttpFileTransferService {
final cs = GetIt.I.get<ConversationService>();
final conversation = (await cs.getConversationByJid(
job.messageKey.conversationJid,
job.conversationJid,
job.accountJid,
))!;
@ -603,8 +598,7 @@ class HttpFileTransferService {
}
final msg = await GetIt.I.get<MessageService>().updateMessage(
job.messageKey.sid,
job.messageKey.conversationJid,
job.messageId,
job.accountJid,
fileMetadata: metadata,
isFileUploadNotification: false,
@ -615,7 +609,7 @@ class HttpFileTransferService {
sendEvent(MessageUpdatedEvent(message: msg));
final updatedConversation = conversation.copyWith(
lastMessage: conversation.lastMessage?.sid == job.messageKey.sid
lastMessage: conversation.lastMessage?.id == job.messageId
? msg
: conversation.lastMessage,
);

View File

@ -55,24 +55,29 @@ class FileUploadJob {
@immutable
class FileDownloadJob {
const FileDownloadJob(
this.messageKey,
this.location,
this.messageId,
this.conversationJid,
this.accountJid,
this.location,
this.metadataId,
this.createMetadataHashes,
this.mimeGuess, {
this.shouldShowNotification = true,
});
/// The message key.
final MessageKey messageKey;
/// The message id.
final String messageId;
/// The JID of the conversation we're downloading the file in.
final String conversationJid;
/// The associated account.
final String accountJid;
/// The location where the file can be found.
final MediaFileLocation location;
/// The associated account
final String accountJid;
/// The id of the file metadata describing the file.
final String metadataId;
@ -89,8 +94,9 @@ class FileDownloadJob {
@override
bool operator ==(Object other) {
return other is FileDownloadJob &&
messageId == other.messageId &&
conversationJid == other.conversationJid &&
location == other.location &&
messageKey == other.messageKey &&
accountJid == other.accountJid &&
metadataId == other.metadataId &&
mimeGuess == other.mimeGuess &&
@ -99,8 +105,9 @@ class FileDownloadJob {
@override
int get hashCode =>
conversationJid.hashCode ^
messageId.hashCode ^
location.hashCode ^
messageKey.hashCode ^
metadataId.hashCode ^
mimeGuess.hashCode ^
shouldShowNotification.hashCode;

View File

@ -17,11 +17,15 @@ import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/models/file_metadata.dart';
import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/shared/warning_types.dart';
import 'package:uuid/uuid.dart';
class MessageService {
/// Logger
final Logger _log = Logger('MessageService');
/// UUID instance for message ids.
final _uuid = const Uuid();
Future<Message> _parseMessage(
Map<String, Object?> rawMessage,
String accountJid,
@ -46,28 +50,33 @@ class MessageService {
fm,
queryReactionPreview
? await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage(
rawMessage['sid']! as String,
rawMessage['conversationJid']! as String,
rawMessage['id']! as String,
accountJid,
)
: [],
);
}
/// Queries the database for a message with a stanza id of [sid] inside
/// the conversation [conversationJid] in the context of the account
/// Queries the database for a message with a stanza id of [id] inside
/// the conversation [conversationJid], if specified, in the context of the account
/// [accountJid].
Future<Message?> getMessageBySid(
String sid,
String conversationJid,
Future<Message?> getMessageById(
String id,
String accountJid, {
String? conversationJid,
bool queryReactionPreview = true,
}) async {
final db = GetIt.I.get<DatabaseService>().database;
final messagesRaw = await db.query(
messagesTable,
where: 'sid = ? AND conversationJid = ? AND accountJid = ?',
whereArgs: [sid, conversationJid, accountJid],
where: conversationJid != null
? 'id = ? AND accountJid = ? AND conversationJid = ?'
: 'id = ? AND accountJid = ?',
whereArgs: [
id,
accountJid,
if (conversationJid != null) conversationJid,
],
limit: 1,
);
@ -77,22 +86,53 @@ class MessageService {
}
/// Queries the database for a message with a stanza id of [originId] inside
/// the conversation [conversationJid] in the context of the account
/// the conversation [conversationJid], if specified, in the context of the account
/// [accountJid].
Future<Message?> getMessageByOriginId(
String originId,
String conversationJid,
String accountJid, {
String? conversationJid,
bool queryReactionPreview = true,
}) async {
final db = GetIt.I.get<DatabaseService>().database;
final messagesRaw = await db.query(
messagesTable,
where: 'conversationJid = ? AND accountJid = ? AND originId = ?',
where: conversationJid != null
? 'accountJid = ? AND originId = ? AND conversationJid = ?'
: 'accountJid = ? AND originId = ?',
whereArgs: [
conversationJid,
accountJid,
originId,
if (conversationJid != null) conversationJid,
],
limit: 1,
);
if (messagesRaw.isEmpty) return null;
// TODO(PapaTutuWawa): Load the quoted message
return _parseMessage(messagesRaw.first, accountJid, queryReactionPreview);
}
/// Query the database for the message with a stanza id of [sid] in the context of [accountJid].
/// If [conversationJid] is specified, then the message must also be within the conversation with
/// [conversationJid].
Future<Message?> getMessageByStanzaId(
String sid,
String accountJid, {
String? conversationJid,
bool queryReactionPreview = true,
}) async {
final db = GetIt.I.get<DatabaseService>().database;
final messagesRaw = await db.query(
messagesTable,
where: conversationJid != null
? 'accountJid = ? AND sid = ? AND conversationJid = ?'
: 'accountJid = ? AND sid = ?',
whereArgs: [
accountJid,
sid,
if (conversationJid != null) conversationJid,
],
limit: 1,
);
@ -135,7 +175,7 @@ SELECT
quote.displayed AS quote_displayed,
quote.acked AS quote_acked,
quote.originId AS quote_originId,
quote.quote_sid AS quote_quote_sid,
quote.quote_id AS quote_quote_id,
quote.file_metadata_id AS quote_file_metadata_id,
quote.isDownloading AS quote_isDownloading,
quote.isUploading AS quote_isUploading,
@ -162,7 +202,7 @@ SELECT
fm.size as fm_size
FROM (SELECT * FROM $messagesTable WHERE $query ORDER BY timestamp DESC LIMIT $messagePaginationSize) AS msg
LEFT JOIN $fileMetadataTable fm ON msg.file_metadata_id = fm.id
LEFT JOIN $messagesTable quote ON msg.quote_sid = quote.sid;
LEFT JOIN $messagesTable quote ON msg.quote_id = quote.id;
''',
[
jid,
@ -178,7 +218,7 @@ FROM (SELECT * FROM $messagesTable WHERE $query ORDER BY timestamp DESC LIMIT $m
}
Message? quotes;
if (m['quote_sid'] != null) {
if (m['quote_id'] != null) {
final rawQuote = getPrefixedSubMap(m, 'quote_');
FileMetadata? quoteFm;
@ -209,8 +249,7 @@ FROM (SELECT * FROM $messagesTable WHERE $query ORDER BY timestamp DESC LIMIT $m
quotes,
fm,
await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage(
m['sid']! as String,
jid,
m['id']! as String,
accountJid,
),
),
@ -296,8 +335,7 @@ FROM
getPrefixedSubMap(m, 'fm_'),
),
await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage(
m['sid']! as String,
m['conversationJid']! as String,
m['id']! as String,
accountJid,
),
),
@ -333,6 +371,7 @@ FROM
}) async {
final db = GetIt.I.get<DatabaseService>().database;
var message = Message(
_uuid.v4(),
accountJid,
sender,
body,
@ -358,7 +397,7 @@ FROM
if (quoteId != null) {
final quotes =
await getMessageBySid(quoteId, conversationJid, accountJid);
await getMessageById(quoteId, accountJid);
if (quotes == null) {
_log.warning('Failed to add quote for message with id $quoteId');
} else {
@ -372,10 +411,9 @@ FROM
/// Wrapper around [DatabaseService]'s updateMessage that updates the cache
Future<Message> updateMessage(
String sid,
String conversationJid,
String id,
String accountJid, {
String? newSid,
String? sid,
Object? body = notSpecified,
bool? received,
bool? displayed,
@ -432,22 +470,21 @@ FROM
if (isEdited != null) {
m['isEdited'] = boolToInt(isEdited);
}
if (newSid != null) {
m['sid'] = newSid;
if (sid != null) {
m['sid'] = sid;
}
final updatedMessage = await db.updateAndReturn(
messagesTable,
m,
where: 'sid = ? AND conversationJid = ? AND accountJid = ?',
whereArgs: [sid, conversationJid, accountJid],
where: 'id = ? AND accountJid = ?',
whereArgs: [id, accountJid],
);
Message? quotes;
if (updatedMessage['quote_sid'] != null) {
quotes = await getMessageBySid(
updatedMessage['quote_sid']! as String,
updatedMessage['conversationJid']! as String,
if (updatedMessage['quote_id'] != null) {
quotes = await getMessageById(
updatedMessage['quote_id']! as String,
accountJid,
queryReactionPreview: false,
);
@ -471,10 +508,9 @@ FROM
updatedMessage,
quotes,
metadata,
// TODO: How should this work with reactions?
await GetIt.I
.get<ReactionsService>()
.getPreviewReactionsForMessage(sid, conversationJid, accountJid),
.getPreviewReactionsForMessage(id, accountJid),
);
return msg;
@ -500,7 +536,6 @@ FROM
) async {
final msg = await getMessageByOriginId(
originId,
conversationJid,
accountJid,
queryReactionPreview: false,
);
@ -524,8 +559,7 @@ FROM
final isMedia = msg.isMedia;
final retractedMessage = await updateMessage(
msg.sid,
msg.conversationJid,
msg.id,
accountJid,
warningType: null,
errorType: null,
@ -539,7 +573,7 @@ FROM
final conversation =
await cs.getConversationByJid(conversationJid, accountJid);
if (conversation != null) {
if (conversation.lastMessage?.sid == msg.sid) {
if (conversation.lastMessage?.id == msg.id) {
final newConversation = conversation.copyWith(
lastMessage: retractedMessage,
);
@ -565,19 +599,17 @@ FROM
}
}
/// Marks the message with the stanza id [sid] as displayed and sends an
/// Marks the message with the message id [id] as displayed and sends an
/// [MessageUpdatedEvent] to the UI. if [sendChatMarker] is true, then
/// a Chat Marker with <displayed /> is sent to the message's
/// conversationJid attribute.
Future<Message> markMessageAsRead(
String sid,
String converationJid,
String id,
String accountJid,
bool sendChatMarker,
) async {
final newMessage = await updateMessage(
sid,
converationJid,
id,
accountJid,
displayed: true,
);

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:math';
import 'package:get_it/get_it.dart';
import 'package:logging/logging.dart';
@ -27,7 +26,7 @@ const _warningChannelKey = 'warning_channel';
/// Message payload keys.
const _conversationJidKey = 'conversationJid';
const _messageKeyKey = 'key';
const _messageIdKey = 'message_id';
const _conversationTitleKey = 'title';
const _conversationAvatarKey = 'avatarPath';
@ -50,13 +49,9 @@ class NotificationsService {
);
} else if (event.type == NotificationEventType.markAsRead) {
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final messageKey = modelm.MessageKey.fromJson(
jsonDecode(event.extra![_messageKeyKey]!) as Map<String, Object>,
);
// Mark the message as read
await GetIt.I.get<MessageService>().markMessageAsRead(
messageKey.sid,
messageKey.conversationJid,
event.extra![_messageIdKey]!,
accountJid,
// [XmppService.sendReadMarker] will check whether the *SHOULD* send
// the marker, i.e. if the privacy settings allow it.
@ -301,7 +296,7 @@ class NotificationsService {
isGroupchat: c.isGroupchat,
extra: {
_conversationJidKey: c.jid,
_messageKeyKey: jsonEncode(m.messageKey),
_messageIdKey: m.id,
_conversationTitleKey: await c.titleWithOptionalContactService,
_conversationAvatarKey: await c.avatarPathWithOptionalContactService,
},
@ -351,7 +346,7 @@ class NotificationsService {
isGroupchat: c.isGroupchat,
extra: {
_conversationJidKey: c.jid,
_messageKeyKey: jsonEncode(m.messageKey),
_messageIdKey: m.id,
_conversationTitleKey: await c.titleWithOptionalContactService,
_conversationAvatarKey: await c.avatarPathWithOptionalContactService,
},

View File

@ -21,17 +21,16 @@ class ReactionWrapper {
class ReactionsService {
final Logger _log = Logger('ReactionsService');
/// Query the database for 6 distinct emoji reactions associated with the message sid
/// [sid].
/// Query the database for 6 distinct emoji reactions associated with the message id
/// [id].
Future<List<String>> getPreviewReactionsForMessage(
String sid,
String conversationJid,
String id,
String accountJid,
) async {
final reactions = await GetIt.I.get<DatabaseService>().database.query(
reactionsTable,
where: 'message_sid = ? AND conversationJid = ? AND accountJid = ?',
whereArgs: [sid, conversationJid, accountJid],
where: 'message_id = ? AND accountJid = ?',
whereArgs: [id, accountJid],
columns: ['emoji'],
distinct: true,
limit: 6,
@ -41,62 +40,58 @@ class ReactionsService {
}
Future<List<Reaction>> getReactionsForMessage(
String sid,
String conversationJid,
String id,
String accountJid,
) async {
final reactions = await GetIt.I.get<DatabaseService>().database.query(
reactionsTable,
where: 'message_sid = ? AND conversationJid = ? AND accountJid = ?',
whereArgs: [sid, conversationJid, accountJid],
where: 'message_id = ? AND accountJid = ?',
whereArgs: [id, accountJid],
);
return reactions.map(Reaction.fromJson).toList();
}
Future<List<String>> getReactionsForMessageByJid(
String sid,
String conversationJid,
String id,
String accountJid,
String jid,
) async {
final reactions = await GetIt.I.get<DatabaseService>().database.query(
reactionsTable,
where:
'message_sid = ? AND conversationJid = ? AND accountJid = ? AND senderJid = ?',
whereArgs: [sid, conversationJid, accountJid, jid],
'message_id = ? AND accountJid = ? AND senderJid = ?',
whereArgs: [id, accountJid, jid],
);
return reactions.map((r) => r['emoji']! as String).toList();
}
Future<int> _countReactions(
String sid,
String conversationJid,
String id,
String accountJid,
String emoji,
) async {
return GetIt.I.get<DatabaseService>().database.count(
reactionsTable,
'message_sid = ? AND conversationJid = ? AND accountJid = ? AND emoji = ?',
[sid, conversationJid, accountJid, emoji],
'message_id = ? AND accountJid = ? AND emoji = ?',
[id, accountJid, emoji],
);
}
/// Adds a new reaction [emoji], if possible, to [sid] and returns the
/// Adds a new reaction [emoji], if possible, to the message with id [id] and returns the
/// new message reaction preview.
Future<Message?> addNewReaction(
String sid,
String conversationJid,
String id,
String accountJid,
String senderJid,
String emoji,
) async {
final ms = GetIt.I.get<MessageService>();
var msg = await ms.getMessageBySid(sid, conversationJid, accountJid);
var msg = await ms.getMessageById(id, accountJid);
if (msg == null) {
_log.warning(
'Failed to get message ($sid, $conversationJid, $accountJid)',
'Failed to get message ($id, $accountJid)',
);
return null;
}
@ -105,11 +100,8 @@ class ReactionsService {
await GetIt.I.get<DatabaseService>().database.insert(
reactionsTable,
Reaction(
sid,
conversationJid,
id,
accountJid,
msg.timestamp,
msg.sender,
senderJid,
emoji,
).toJson(),
@ -129,17 +121,16 @@ class ReactionsService {
}
Future<Message?> removeReaction(
String sid,
String conversationJid,
String id,
String accountJid,
String senderJid,
String emoji,
) async {
final ms = GetIt.I.get<MessageService>();
final msg = await ms.getMessageBySid(sid, conversationJid, accountJid);
final msg = await ms.getMessageById(id, accountJid);
if (msg == null) {
_log.warning(
'Failed to get message ($sid, $conversationJid, $accountJid)',
'Failed to get message ($id, $accountJid)',
);
return null;
}
@ -147,17 +138,16 @@ class ReactionsService {
await GetIt.I.get<DatabaseService>().database.delete(
reactionsTable,
where:
'message_sid = ? AND conversationJid = ? AND accountJid = ? AND emoji = ? AND senderJid = ?',
'message_id = ? AND accountJid = ? AND emoji = ? AND senderJid = ?',
whereArgs: [
sid,
conversationJid,
id,
accountJid,
emoji,
(await GetIt.I.get<XmppStateService>().getXmppState()).jid,
],
);
final count =
await _countReactions(sid, conversationJid, accountJid, emoji);
await _countReactions(id, accountJid, emoji);
if (count > 0) {
return msg;
@ -177,7 +167,7 @@ class ReactionsService {
) async {
// Get all reactions know for this message
final allReactions =
await getReactionsForMessage(msg.sid, msg.conversationJid, accountJid);
await getReactionsForMessage(msg.id, accountJid);
final userEmojis =
allReactions.where((r) => r.senderJid == senderJid).map((r) => r.emoji);
final removedReactions = userEmojis.where((e) => !emojis.contains(e));
@ -189,8 +179,8 @@ class ReactionsService {
final rows = await db.delete(
reactionsTable,
where:
'message_sid = ? AND conversationJid = ? AND accountJid = ? AND senderJid = ? AND emoji = ?',
whereArgs: [msg.sid, msg.conversationJid, accountJid, senderJid, emoji],
'message_id = ? AND accountJid = ? AND senderJid = ? AND emoji = ?',
whereArgs: [msg.id, accountJid, senderJid, emoji],
);
assert(rows == 1, 'Only one row should be removed');
}
@ -199,11 +189,8 @@ class ReactionsService {
await db.insert(
reactionsTable,
Reaction(
msg.sid,
msg.conversationJid,
msg.id,
accountJid,
msg.timestamp,
msg.sender,
senderJid,
emoji,
).toJson(),
@ -212,8 +199,7 @@ class ReactionsService {
final newMessage = msg.copyWith(
reactionsPreview: await getPreviewReactionsForMessage(
msg.sid,
msg.conversationJid,
msg.id,
accountJid,
),
);

View File

@ -144,14 +144,14 @@ class XmppService {
String? getCurrentlyOpenedChatJid() => _currentlyOpenedChatJid;
/// Sends a message correction to [recipient] regarding the message with stanza id
/// [oldId]. The old message's body gets corrected to [newBody]. [sid] is the message's
/// stanza id. [chatState] can be optionally specified to also include a chat state
/// [oldId]. The old message's body gets corrected to [newBody]. [id] is the message's
/// id. [chatState] can be optionally specified to also include a chat state
/// in the message.
///
/// This function handles updating the message and optionally the corresponding
/// conversation.
Future<void> sendMessageCorrection(
String sid,
String id,
String conversationJid,
String accountJid,
String newBody,
@ -166,8 +166,7 @@ class XmppService {
// Update the database
final msg = await ms.updateMessage(
sid,
conversationJid,
id,
accountJid,
isEdited: true,
body: newBody,
@ -178,7 +177,7 @@ class XmppService {
recipient,
accountJid,
update: (c) async {
if (c.lastMessage?.sid == sid) {
if (c.lastMessage?.id == id) {
return cs.updateConversation(
c.jid,
accountJid,
@ -944,9 +943,8 @@ class XmppService {
final cs = GetIt.I.get<ConversationService>();
final sender = event.from.toBare().toString();
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final dbMsg = await ms.getMessageBySid(
final dbMsg = await ms.getMessageByOriginId(
event.id,
sender,
accountJid,
queryReactionPreview: false,
);
@ -958,8 +956,7 @@ class XmppService {
}
final msg = await ms.updateMessage(
dbMsg.sid,
sender,
dbMsg.id,
accountJid,
received: true,
);
@ -967,7 +964,7 @@ class XmppService {
// Update the conversation
final conv = await cs.getConversationByJid(sender, accountJid);
if (conv != null && conv.lastMessage?.sid == msg.sid) {
if (conv != null && conv.lastMessage?.id == msg.id) {
final newConv = conv.copyWith(lastMessage: msg);
cs.setConversation(newConv);
_log.finest('Updating conversation');
@ -983,15 +980,14 @@ class XmppService {
final sender = event.from.toBare().toString();
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
// TODO(Unknown): With groupchats, we should use the groupchat assigned stanza-id
final dbMsg = await ms.getMessageBySid(event.id, sender, accountJid);
final dbMsg = await ms.getMessageByOriginId(event.id, accountJid);
if (dbMsg == null) {
_log.warning('Did not find the message in the database!');
return;
}
final msg = await ms.updateMessage(
dbMsg.sid,
sender,
dbMsg.id,
accountJid,
received: dbMsg.received ||
event.type == ChatMarker.received ||
@ -1005,7 +1001,7 @@ class XmppService {
// Update the conversation
final conv = await cs.getConversationByJid(sender, accountJid);
if (conv != null && conv.lastMessage?.sid == msg.sid) {
if (conv != null && conv.lastMessage?.id == msg.id) {
final newConv = conv.copyWith(lastMessage: msg);
cs.setConversation(newConv);
_log.finest('Updating conversation');
@ -1156,9 +1152,8 @@ class XmppService {
}
final ms = GetIt.I.get<MessageService>();
final msg = await ms.getMessageBySid(
final msg = await ms.getMessageByStanzaId(
event.id!,
event.from.toBare().toString(),
accountJid,
);
@ -1177,8 +1172,7 @@ class XmppService {
}
final newMsg = await ms.updateMessage(
msg.sid,
msg.conversationJid,
msg.id,
accountJid,
errorType: error,
);
@ -1201,8 +1195,7 @@ class XmppService {
final cs = GetIt.I.get<ConversationService>();
final correctionId = event.extensions.get<LastMessageCorrectionData>()!.id;
final msg = await ms.getMessageBySid(
conversationJid,
final msg = await ms.getMessageByOriginId(
correctionId,
accountJid,
);
@ -1231,8 +1224,7 @@ class XmppService {
// TODO(Unknown): Should we null-check here?
final newMsg = await ms.updateMessage(
msg.sid,
msg.conversationJid,
msg.id,
accountJid,
body: event.extensions.get<MessageBodyData>()!.body,
isEdited: true,
@ -1240,7 +1232,7 @@ class XmppService {
sendEvent(MessageUpdatedEvent(message: newMsg));
final conv = await cs.getConversationByJid(msg.conversationJid, accountJid);
if (conv != null && conv.lastMessage?.sid == msg.sid) {
if (conv != null && conv.lastMessage?.id == msg.id) {
final newConv = conv.copyWith(
lastMessage: newMsg,
);
@ -1257,9 +1249,8 @@ class XmppService {
final ms = GetIt.I.get<MessageService>();
// TODO(Unknown): Once we support groupchats, we need to instead query by the stanza-id
final reactions = event.extensions.get<MessageReactionsData>()!;
final msg = await ms.getMessageBySid(
final msg = await ms.getMessageByOriginId(
reactions.messageId,
conversationJid,
accountJid,
queryReactionPreview: false,
);
@ -1494,16 +1485,16 @@ class XmppService {
(metadata.size != null &&
metadata.size! < prefs.maximumAutoDownloadSize * 1000000)) {
message = await ms.updateMessage(
message.sid,
message.conversationJid,
message.id,
accountJid,
isDownloading: true,
);
await fts.downloadFile(
FileDownloadJob(
message.messageKey,
embeddedFile,
message.id,
message.conversationJid,
accountJid,
embeddedFile,
message.fileMetadata!.id,
// If we did not retrieve the file, then we were not able to find it using
// hashes.
@ -1620,8 +1611,7 @@ class XmppService {
// Mark the file as downlading when it includes a File Upload Notification
if (fun != null) {
message = await ms.updateMessage(
message.sid,
message.conversationJid,
message.id,
accountJid,
isDownloading: true,
);
@ -1641,7 +1631,7 @@ class XmppService {
final replacementId =
event.extensions.get<FileUploadNotificationReplacementData>()!.id;
var message =
await ms.getMessageBySid(replacementId, conversationJid, accountJid);
await ms.getMessageByOriginId(replacementId, accountJid);
if (message == null) {
_log.warning(
'Received a FileUploadNotification replacement for unknown message',
@ -1681,13 +1671,12 @@ class XmppService {
final oldFileMetadata = message.fileMetadata;
message = await ms.updateMessage(
message.sid,
message.conversationJid,
message.id,
accountJid,
fileMetadata: fileMetadata ?? notSpecified,
isFileUploadNotification: false,
isDownloading: shouldDownload,
newSid: event.id,
sid: event.id,
originId: event.extensions.get<StableIdData>()?.originId,
);
@ -1705,9 +1694,10 @@ class XmppService {
_log.finest('Advertised file MIME: ${_getMimeGuess(event)}');
await GetIt.I.get<HttpFileTransferService>().downloadFile(
FileDownloadJob(
message.messageKey,
embeddedFile,
message.id,
message.conversationJid,
accountJid,
embeddedFile,
oldFileMetadata!.id,
// If [fileMetadata] is null, then we were not able to find the file metadata
// using hashes and thus have to create hash pointers.
@ -1740,12 +1730,11 @@ class XmppService {
final ms = GetIt.I.get<MessageService>();
final cs = GetIt.I.get<ConversationService>();
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final msg = await ms.getMessageBySid(jid, event.stanza.id!, accountJid);
final msg = await ms.getMessageByStanzaId(event.stanza.id!, accountJid);
if (msg != null) {
// Ack the message
final newMsg = await ms.updateMessage(
msg.sid,
msg.conversationJid,
msg.id,
accountJid,
acked: true,
);
@ -1753,7 +1742,7 @@ class XmppService {
// Ack the conversation
final conv = await cs.getConversationByJid(jid, accountJid);
if (conv != null && conv.lastMessage?.sid == newMsg.sid) {
if (conv != null && conv.lastMessage?.id == newMsg.id) {
final newConv = conv.copyWith(lastMessage: msg);
cs.setConversation(newConv);
sendEvent(ConversationUpdatedEvent(conversation: newConv));
@ -1798,12 +1787,9 @@ class XmppService {
if (event.data.stanza.tag != 'message') return;
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final conversationJid =
JID.fromString(event.data.stanza.to!).toBare().toString();
final ms = GetIt.I.get<MessageService>();
final message = await ms.getMessageBySid(
final message = await ms.getMessageByStanzaId(
event.data.stanza.id!,
conversationJid,
accountJid,
);
@ -1816,8 +1802,7 @@ class XmppService {
_log.finest('Cancel reason: ${event.data.cancelReason}');
final newMessage = await ms.updateMessage(
message.sid,
conversationJid,
message.id,
accountJid,
errorType: MessageErrorType.fromException(event.data.cancelReason),
);

View File

@ -197,7 +197,7 @@ class Conversation with _$Conversation {
'open': boolToInt(open),
'muted': boolToInt(muted),
'encrypted': boolToInt(encrypted),
'lastMessageId': lastMessage?.sid,
'lastMessageId': lastMessage?.id,
};
}

View File

@ -9,57 +9,6 @@ import 'package:moxxyv2/shared/warning_types.dart';
part 'message.freezed.dart';
part 'message.g.dart';
/// A composite key to replace the old incrementing integer id attribute.
/// Somewhat mimicks the message table's primary key.
@immutable
class MessageKey {
const MessageKey(
this.sender,
this.conversationJid,
this.sid,
this.timestamp,
);
factory MessageKey.fromJson(Map<String, dynamic> json) {
final map = json.cast<String, Object>();
return MessageKey(
map['sender']! as String,
map['conversationJid']! as String,
map['sid']! as String,
map['timestamp']! as int,
);
}
final String conversationJid;
final String sender;
final String sid;
final int timestamp;
Map<String, Object> toJson() => {
'conversationJid': conversationJid,
'sender': sender,
'sid': sid,
'timestamp': timestamp,
};
@override
bool operator ==(Object other) {
return other is MessageKey &&
other.conversationJid == conversationJid &&
other.sender == sender &&
other.sid == sid &&
other.timestamp == timestamp;
}
@override
int get hashCode => sender.hashCode ^ sid.hashCode ^ timestamp.hashCode;
@override
String toString() {
return 'MessageKey($sender, $sid, $timestamp)';
}
}
enum PseudoMessageType {
/// Indicates that a new device was created in the chat.
newDevice(1),
@ -102,6 +51,9 @@ class PseudoMessageTypeConverter extends JsonConverter<PseudoMessageType, int> {
@freezed
class Message with _$Message {
factory Message(
// The message id (Moxxy-generated UUID).
String id,
/// The JID of the account that sent or received the message.
String accountJid,
@ -231,7 +183,7 @@ class Message with _$Message {
'encrypted': boolToInt(encrypted),
'file_metadata_id': fileMetadata?.id,
// NOTE: Message.quote_id is a foreign-key
'quote_sid': quotes?.sid,
'quote_id': quotes?.id,
'isDownloading': boolToInt(isDownloading),
'isUploading': boolToInt(isUploading),
'isRetracted': boolToInt(isRetracted),
@ -343,12 +295,4 @@ class Message with _$Message {
/// The JID of the sender in moxxmpp's format.
JID get senderJid => JID.fromString(sender);
/// A "unique" key for a message.
MessageKey get messageKey => MessageKey(
sender,
conversationJid,
sid,
timestamp,
);
}

View File

@ -8,22 +8,11 @@ class Reaction with _$Reaction {
factory Reaction(
// This is valid in combination with freezed
// ignore: invalid_annotation_target
@JsonKey(name: 'message_sid') String messageSid,
/// The JID of the conversation this reaction is in.
String conversationJid,
@JsonKey(name: 'message_id') String messageId,
// The account JID of the attached message.
String accountJid,
// The timestamp of the referenced message. Required for database reasons.
// ignore: invalid_annotation_target
@JsonKey(name: 'message_timestamp') int timestamp,
// The sender of the referenced message. Required for database reasons.
// ignore: invalid_annotation_target
@JsonKey(name: 'message_sender') String messageSender,
// The sender of the reaction.
String senderJid,

View File

@ -287,7 +287,7 @@ class BidirectionalConversationController
if (newMessage.timestamp < cache.first.timestamp) return;
replaceItem(
(msg) => msg.sid == newMessage.sid && msg.sender == newMessage.sender,
(msg) => msg.id == newMessage.id,
newMessage,
);
}
@ -352,9 +352,7 @@ class BidirectionalConversationController
if (!hasNewerData) {
if (wasEditing) {
foundMessage = replaceItem(
(message) =>
message.sid == result.message.sid &&
message.sender == result.message.sender,
(message) => message.id == result.message.id,
result.message,
);
} else {

View File

@ -140,7 +140,7 @@ Future<void> onRosterPush(RosterDiffEvent event, {dynamic extra}) async {
}
Future<void> onProgress(ProgressEvent event, {dynamic extra}) async {
GetIt.I.get<UIProgressService>().onProgress(event.key, event.progress);
GetIt.I.get<UIProgressService>().onProgress(event.id, event.progress);
}
Future<void> onSelfAvatarChanged(

View File

@ -53,9 +53,7 @@ int getMessageMenuOptionCount(
message.isReactable,
message.canRetract(sentBySelf),
// TODO(Unknown): Remove this and just allow us to correct any message
message.canEdit(sentBySelf) &&
lastMessage?.sid == message.sid &&
lastMessage?.sender == message.sender,
message.canEdit(sentBySelf) && lastMessage?.id == message.id,
message.errorMenuVisible,
message.hasWarning,
message.isCopyable,
@ -105,7 +103,7 @@ class ConversationPageState extends State<ConversationPage>
late final Animation<double> _scrollToBottomAnimation;
late final StreamSubscription<bool> _scrolledToBottomStateSubscription;
final Map<MessageKey, GlobalKey> _messageKeys = {};
final Map<String, GlobalKey> _messageKeys = {};
@override
void initState() {
@ -279,11 +277,11 @@ class ConversationPageState extends State<ConversationPage>
// Give each bubble its own animation and animation controller
GlobalKey key;
if (!_messageKeys.containsKey(item.messageKey)) {
if (!_messageKeys.containsKey(item.id)) {
key = GlobalKey();
_messageKeys[item.messageKey] = key;
_messageKeys[item.id] = key;
} else {
key = _messageKeys[item.messageKey]!;
key = _messageKeys[item.id]!;
}
final bubble = RawChatBubble(

View File

@ -198,7 +198,7 @@ class SelectedMessageContextMenu extends StatelessWidget {
.getDataSender()
.sendData(
AddReactionToMessageCommand(
key: message.messageKey,
id: message.id,
emoji: emoji,
),
awaitable: false,

View File

@ -131,6 +131,7 @@ class NewConversationPage extends StatelessWidget {
'',
item.title,
Message(
'',
'',
'',
item.jid,

View File

@ -1,27 +1,24 @@
import 'package:logging/logging.dart';
import 'package:moxxyv2/shared/models/message.dart';
typedef UIProgressCallback = void Function(double?);
/// This class handles download progress notifications from the backend and relays them
/// to the correct ChatBubble instance so that it can update itself.
class UIProgressService {
UIProgressService()
: _callbacks = {},
_log = Logger('UIProgressService');
/// Logger.
final Logger _log = Logger('UIProgressService');
final Logger _log;
// Database message id -> callback function
final Map<MessageKey, UIProgressCallback> _callbacks;
final Map<String, UIProgressCallback> _callbacks = {};
void registerCallback(MessageKey key, UIProgressCallback callback) {
_log.finest('Registering callback for $key');
_callbacks[key] = callback;
void registerCallback(String id, UIProgressCallback callback) {
_log.finest('Registering callback for $id');
_callbacks[id] = callback;
}
void unregisterCallback(MessageKey key) {
_log.finest('Unregistering callback for $key');
_callbacks.remove(key);
void unregisterCallback(String id) {
_log.finest('Unregistering callback for $id');
_callbacks.remove(id);
}
void unregisterAll() {
@ -29,15 +26,15 @@ class UIProgressService {
_callbacks.clear();
}
void onProgress(MessageKey key, double? progress) {
if (_callbacks.containsKey(key)) {
void onProgress(String id, double? progress) {
if (_callbacks.containsKey(id)) {
if (progress == 1.0) {
unregisterCallback(key);
unregisterCallback(id);
} else {
_callbacks[key]!(progress);
_callbacks[id]!(progress);
}
} else {
_log.warning('Received progress callback for unregistered key $key');
_log.warning('Received progress callback for unregistered key $id');
}
}
}

View File

@ -13,15 +13,15 @@ class UIReadMarkerService {
final Logger _log = Logger('UIReadMarkerService');
/// The cache of messages we already processed.
final Map<MessageKey, bool> _messages = {};
final Map<String, bool> _messages = {};
/// Checks if we should send a read marker for [message]. If we should, tells
/// the backend to actually send it.
void handleMarker(Message message) {
if (_messages.containsKey(message.messageKey)) return;
if (_messages.containsKey(message.id)) return;
// Make sure we don't reach here anymore.
_messages[message.messageKey] = true;
_messages[message.id] = true;
// Only send this for messages we have not yet marked as read.
if (message.displayed) return;
@ -29,12 +29,10 @@ class UIReadMarkerService {
// Check if we should send markers.
if (!GetIt.I.get<PreferencesBloc>().state.sendChatMarkers) return;
final id = message.originId ?? message.sid;
_log.finest('Sending chat marker for ${message.conversationJid}:$id');
_log.finest('Sending chat marker for ${message.id}');
MoxplatformPlugin.handler.getDataSender().sendData(
MarkMessageAsReadCommand(
sid: message.sid,
conversationJid: message.conversationJid,
id: message.id,
sendMarker: true,
),
awaitable: false,

View File

@ -33,7 +33,7 @@ class _AudioWidget extends StatelessWidget {
this.onChanged,
this.duration,
this.position,
this.messageKey,
this.messageId,
);
final double maxWidth;
final bool isDownloading;
@ -42,14 +42,14 @@ class _AudioWidget extends StatelessWidget {
final double? duration;
final double? position;
final Widget? icon;
final MessageKey messageKey;
final String messageId;
Widget _getLeftWidget() {
if (isDownloading) {
return SizedBox(
width: 48,
height: 48,
child: ProgressWidget(messageKey),
child: ProgressWidget(messageId),
);
}
@ -174,7 +174,7 @@ class AudioChatState extends State<AudioChatWidget> {
(_) {},
null,
null,
widget.message.messageKey,
widget.message.id,
),
MessageBubbleBottom(widget.message, widget.sent),
widget.radius,
@ -192,7 +192,7 @@ class AudioChatState extends State<AudioChatWidget> {
(_) {},
null,
null,
widget.message.messageKey,
widget.message.id,
),
MessageBubbleBottom(widget.message, widget.sent),
widget.radius,
@ -237,7 +237,7 @@ class AudioChatState extends State<AudioChatWidget> {
},
_duration,
_position,
widget.message.messageKey,
widget.message.id,
),
MessageBubbleBottom(
widget.message,

View File

@ -147,7 +147,7 @@ class FileChatWidget extends StatelessWidget {
maxWidth,
sent,
mimeType: message.fileMetadata!.filename,
downloadButton: ProgressWidget(message.messageKey),
downloadButton: ProgressWidget(message.id),
);
}

View File

@ -30,7 +30,7 @@ class ImageChatWidget extends StatelessWidget {
Image.file(File(message.fileMetadata!.path!)),
MessageBubbleBottom(message, sent),
radius,
extra: ProgressWidget(message.messageKey),
extra: ProgressWidget(message.id),
);
}
@ -50,7 +50,7 @@ class ImageChatWidget extends StatelessWidget {
),
MessageBubbleBottom(message, sent),
radius,
extra: ProgressWidget(message.messageKey),
extra: ProgressWidget(message.id),
);
} else {
return FileChatBaseWidget(
@ -60,7 +60,7 @@ class ImageChatWidget extends StatelessWidget {
maxWidth,
sent,
mimeType: message.fileMetadata!.mimeType,
downloadButton: ProgressWidget(message.messageKey),
downloadButton: ProgressWidget(message.id),
);
}
}

View File

@ -41,7 +41,7 @@ class VideoChatWidget extends StatelessWidget {
),
MessageBubbleBottom(message, sent),
radius,
extra: ProgressWidget(message.messageKey),
extra: ProgressWidget(message.id),
);
}
@ -61,7 +61,7 @@ class VideoChatWidget extends StatelessWidget {
),
MessageBubbleBottom(message, sent),
radius,
extra: ProgressWidget(message.messageKey),
extra: ProgressWidget(message.id),
);
} else {
return FileChatBaseWidget(
@ -71,7 +71,7 @@ class VideoChatWidget extends StatelessWidget {
maxWidth,
sent,
mimeType: message.fileMetadata!.mimeType,
downloadButton: ProgressWidget(message.messageKey),
downloadButton: ProgressWidget(message.id),
);
}
}

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/ui/constants.dart';
import 'package:moxxyv2/ui/service/progress.dart';
@ -9,8 +8,8 @@ import 'package:moxxyv2/ui/service/progress.dart';
// we, for example, use blurhash, then we compute the image from the blurhash on every
// update.
class ProgressWidget extends StatefulWidget {
const ProgressWidget(this.messageKey, {super.key});
final MessageKey messageKey;
const ProgressWidget(this.messageId, {super.key});
final String messageId;
@override
ProgressWidgetState createState() => ProgressWidgetState();
@ -31,7 +30,7 @@ class ProgressWidgetState extends State<ProgressWidget> {
// Register against the DownloadService
GetIt.I
.get<UIProgressService>()
.registerCallback(widget.messageKey, _onProgressUpdate);
.registerCallback(widget.messageId, _onProgressUpdate);
super.initState();
}
@ -39,7 +38,7 @@ class ProgressWidgetState extends State<ProgressWidget> {
@override
void dispose() {
// Unregister
GetIt.I.get<UIProgressService>().unregisterCallback(widget.messageKey);
GetIt.I.get<UIProgressService>().unregisterCallback(widget.messageId);
super.dispose();
}

View File

@ -4,7 +4,6 @@ import 'package:moxplatform/moxplatform.dart';
import 'package:moxxyv2/i18n/strings.g.dart';
import 'package:moxxyv2/shared/commands.dart';
import 'package:moxxyv2/shared/events.dart';
import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/shared/models/reaction_group.dart';
import 'package:moxxyv2/ui/bloc/conversations_bloc.dart';
import 'package:moxxyv2/ui/helpers.dart';
@ -39,17 +38,17 @@ List<ReactionGroup> ensureReactionGroupOrder(
/// Displays the reactions to a message and allows modifying the reactions.
/// When created, fetches the reactions from the ReactionService.
class ReactionList extends StatelessWidget {
const ReactionList(this.messageKey, {super.key});
const ReactionList(this.messageId, {super.key});
/// The database identifier of the message to fetch reactions of.
final MessageKey messageKey;
final String messageId;
@override
Widget build(BuildContext context) {
return FutureBuilder<BackgroundEvent?>(
future: MoxplatformPlugin.handler.getDataSender().sendData(
GetReactionsForMessageCommand(
key: messageKey,
id: messageId,
),
) as Future<BackgroundEvent?>,
builder: (context, snapshot) {
@ -104,7 +103,7 @@ class ReactionList extends StatelessWidget {
.getDataSender()
.sendData(
AddReactionToMessageCommand(
key: messageKey,
id: messageId,
emoji: emoji,
),
awaitable: false,
@ -116,7 +115,7 @@ class ReactionList extends StatelessWidget {
? (emoji) async {
await MoxplatformPlugin.handler.getDataSender().sendData(
RemoveReactionFromMessageCommand(
key: messageKey,
id: messageId,
emoji: emoji,
),
awaitable: false,

View File

@ -33,7 +33,7 @@ class ReactionsPreview extends StatelessWidget {
),
builder: (context) {
return ReactionList(
message.messageKey,
message.id,
);
},
);