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

View File

@ -84,9 +84,8 @@ class ConversationService {
Message? lastMessage; Message? lastMessage;
if (c['lastMessageId'] != null) { if (c['lastMessageId'] != null) {
lastMessage = await GetIt.I.get<MessageService>().getMessageBySid( lastMessage = await GetIt.I.get<MessageService>().getMessageById(
c['lastMessageId']! as String, c['lastMessageId']! as String,
jid,
accountJid, accountJid,
queryReactionPreview: false, queryReactionPreview: false,
); );
@ -173,7 +172,7 @@ class ConversationService {
final c = <String, dynamic>{}; final c = <String, dynamic>{};
if (lastMessage != null) { if (lastMessage != null) {
c['lastMessageId'] = lastMessage.sid; c['lastMessageId'] = lastMessage.id;
} }
if (lastChangeTimestamp != null) { if (lastChangeTimestamp != null) {
c['lastChangeTimestamp'] = lastChangeTimestamp; 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/database/constants.dart';
import 'package:moxxyv2/service/xmpp_state.dart'; import 'package:moxxyv2/service/xmpp_state.dart';
import 'package:sqflite_sqlcipher/sqflite.dart'; import 'package:sqflite_sqlcipher/sqflite.dart';
import 'package:uuid/uuid.dart';
extension MaybeGet<K, V> on Map<K, V> { extension MaybeGet<K, V> on Map<K, V> {
V? maybeGet(K? key) { V? maybeGet(K? key) {
@ -65,6 +66,7 @@ Future<void> upgradeFromV45ToV46(Database db) async {
await db.execute( await db.execute(
''' '''
CREATE TABLE ${messagesTable}_new ( CREATE TABLE ${messagesTable}_new (
id TEXT PRIMARY KEY,
accountJid TEXT NOT NULL, accountJid TEXT NOT NULL,
sender TEXT NOT NULL, sender TEXT NOT NULL,
body TEXT, body TEXT,
@ -79,7 +81,7 @@ Future<void> upgradeFromV45ToV46(Database db) async {
displayed INTEGER, displayed INTEGER,
acked INTEGER, acked INTEGER,
originId TEXT, originId TEXT,
quote_sid TEXT, quote_id TEXT,
file_metadata_id TEXT, file_metadata_id TEXT,
isDownloading INTEGER NOT NULL, isDownloading INTEGER NOT NULL,
isUploading INTEGER NOT NULL, isUploading INTEGER NOT NULL,
@ -89,30 +91,24 @@ Future<void> upgradeFromV45ToV46(Database db) async {
stickerPackId TEXT, stickerPackId TEXT,
pseudoMessageType INTEGER, pseudoMessageType INTEGER,
pseudoMessageData TEXT, pseudoMessageData TEXT,
PRIMARY KEY (accountJid, sender, sid, timestamp, conversationJid),
CONSTRAINT fk_quote CONSTRAINT fk_quote
FOREIGN KEY (accountJid, quote_sid) FOREIGN KEY (accountJid, quote_id)
REFERENCES $messagesTable (accountJid, sid) REFERENCES $messagesTable (accountJid, id)
CONSTRAINT fk_file_metadata CONSTRAINT fk_file_metadata
FOREIGN KEY (file_metadata_id) FOREIGN KEY (file_metadata_id)
REFERENCES $fileMetadataTable (id) REFERENCES $fileMetadataTable (id)
)''', )''',
); );
// Build up the message map // 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 messageMap = <int, String>{};
final conversationMap = <int, String>{};
final timestampMap = <int, int>{};
final senderMap = <int, String>{};
if (migrateRows) { if (migrateRows) {
final messages = await db.query(messagesTable); final messages = await db.query(messagesTable);
for (final message in messages) { for (final message in messages) {
messageMap[message['id']! as int] = message['sid']! as String; messageMap[message['id']! as int] = uuid.v4();
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;
} }
// Then migrate messages // Then migrate messages
for (final message in messages) { for (final message in messages) {
@ -121,7 +117,8 @@ Future<void> upgradeFromV45ToV46(Database db) async {
..remove('id') ..remove('id')
..remove('quote_id'), ..remove('quote_id'),
'accountJid': accountJid, '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), PRIMARY KEY (jid, accountJid),
CONSTRAINT fk_last_message CONSTRAINT fk_last_message
FOREIGN KEY (accountJid, lastMessageId) FOREIGN KEY (accountJid, lastMessageId)
REFERENCES $messagesTable (accountJid, sid), REFERENCES $messagesTable (accountJid, id),
CONSTRAINT fk_contact_id CONSTRAINT fk_contact_id
FOREIGN KEY (contactId) FOREIGN KEY (contactId)
REFERENCES $contactsTable (id) REFERENCES $contactsTable (id)
@ -188,17 +185,14 @@ Future<void> upgradeFromV45ToV46(Database db) async {
await db.execute( await db.execute(
''' '''
CREATE TABLE ${reactionsTable}_new ( CREATE TABLE ${reactionsTable}_new (
accountJid TEXT NOT NULL, accountJid TEXT NOT NULL,
message_timestamp INTEGER NOT NULL, message_id TEXT NOT NULL,
message_sender TEXT NOT NULL, senderJid TEXT NOT NULL,
senderJid TEXT NOT NULL, emoji TEXT NOT NULL,
emoji TEXT NOT NULL, PRIMARY KEY (accountJid, senderJid, emoji, message_id),
message_sid TEXT NOT NULL,
conversationJid TEXT NOT NULL,
PRIMARY KEY (accountJid, senderJid, emoji, message_sid, conversationJid, message_timestamp, message_sender),
CONSTRAINT fk_message CONSTRAINT fk_message
FOREIGN KEY (accountJid, message_sid, conversationJid, message_timestamp, message_sender) FOREIGN KEY (accountJid, message_id)
REFERENCES $messagesTable (accountJid, sid, conversationJid, timestamp, sender) REFERENCES $messagesTable (accountJid, id)
ON DELETE CASCADE ON DELETE CASCADE
)''', )''',
); );
@ -207,12 +201,10 @@ Future<void> upgradeFromV45ToV46(Database db) async {
await db.insert( await db.insert(
'${reactionsTable}_new', '${reactionsTable}_new',
{ {
...Map.from(reaction)..remove('message_id'), ...Map.from(reaction)
'message_sid': messageMap.maybeGet(reaction['message_id']! as int), ..remove('message_id'),
'conversationJid': conversationMap[reaction['message_id']! as int], 'message_id': messageMap.maybeGet(reaction['message_id']! as int),
'accountJid': accountJid, '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 accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final message = await ms.updateMessage( final message = await ms.updateMessage(
command.message.sid, command.message.id,
command.message.conversationJid,
accountJid, accountJid,
isDownloading: true, isDownloading: true,
); );
@ -687,7 +686,9 @@ Future<void> performRequestDownload(
await srv.downloadFile( await srv.downloadFile(
FileDownloadJob( FileDownloadJob(
message.messageKey, message.id,
message.conversationJid,
accountJid,
MediaFileLocation( MediaFileLocation(
fileMetadata.sourceUrls!, fileMetadata.sourceUrls!,
fileMetadata.filename, fileMetadata.filename,
@ -702,7 +703,6 @@ Future<void> performRequestDownload(
fileMetadata.ciphertextHashes, fileMetadata.ciphertextHashes,
null, null,
), ),
accountJid,
message.fileMetadata!.id, message.fileMetadata!.id,
message.fileMetadata!.plaintextHashes?.isNotEmpty ?? false, message.fileMetadata!.plaintextHashes?.isNotEmpty ?? false,
mimeGuess, mimeGuess,
@ -1056,8 +1056,7 @@ Future<void> performMarkConversationAsRead(
if (conversation.lastMessage != null) { if (conversation.lastMessage != null) {
await GetIt.I.get<MessageService>().markMessageAsRead( await GetIt.I.get<MessageService>().markMessageAsRead(
conversation.lastMessage!.sid, conversation.lastMessage!.id,
conversation.lastMessage!.conversationJid,
accountJid, accountJid,
conversation.type != ConversationType.note, conversation.type != ConversationType.note,
); );
@ -1077,8 +1076,7 @@ Future<void> performMarkMessageAsRead(
}) async { }) async {
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid(); final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
await GetIt.I.get<MessageService>().markMessageAsRead( await GetIt.I.get<MessageService>().markMessageAsRead(
command.sid, command.id,
command.conversationJid,
accountJid, accountJid,
command.sendMarker, command.sendMarker,
); );
@ -1091,8 +1089,7 @@ Future<void> performAddMessageReaction(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid(); final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final rs = GetIt.I.get<ReactionsService>(); final rs = GetIt.I.get<ReactionsService>();
final msg = await rs.addNewReaction( final msg = await rs.addNewReaction(
command.key.sid, command.id,
command.key.conversationJid,
accountJid, accountJid,
accountJid, accountJid,
command.emoji, command.emoji,
@ -1108,19 +1105,18 @@ Future<void> performAddMessageReaction(
), ),
); );
if (command.key.conversationJid != '') { if (msg.conversationJid != '') {
// Send the reaction // Send the reaction
final manager = GetIt.I final manager = GetIt.I
.get<XmppConnection>() .get<XmppConnection>()
.getManagerById<MessageManager>(messageManager)!; .getManagerById<MessageManager>(messageManager)!;
await manager.sendMessage( await manager.sendMessage(
JID.fromString(command.key.conversationJid), JID.fromString(msg.conversationJid),
TypedMap<StanzaHandlerExtension>.fromList([ TypedMap<StanzaHandlerExtension>.fromList([
MessageReactionsData( MessageReactionsData(
msg.originId ?? msg.sid, msg.originId ?? msg.sid,
await rs.getReactionsForMessageByJid( await rs.getReactionsForMessageByJid(
command.key.sid, command.id,
command.key.conversationJid,
accountJid, accountJid,
accountJid, accountJid,
), ),
@ -1140,8 +1136,7 @@ Future<void> performRemoveMessageReaction(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid(); final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final rs = GetIt.I.get<ReactionsService>(); final rs = GetIt.I.get<ReactionsService>();
final msg = await rs.removeReaction( final msg = await rs.removeReaction(
command.key.sid, command.id,
command.key.conversationJid,
accountJid, accountJid,
accountJid, accountJid,
command.emoji, command.emoji,
@ -1158,19 +1153,18 @@ Future<void> performRemoveMessageReaction(
); );
if (command.key.conversationJid != '') { if (msg.conversationJid != '') {
// Send the reaction // Send the reaction
final manager = GetIt.I final manager = GetIt.I
.get<XmppConnection>() .get<XmppConnection>()
.getManagerById<MessageManager>(messageManager)!; .getManagerById<MessageManager>(messageManager)!;
await manager.sendMessage( await manager.sendMessage(
JID.fromString(command.key.conversationJid), JID.fromString(msg.conversationJid),
TypedMap<StanzaHandlerExtension>.fromList([ TypedMap<StanzaHandlerExtension>.fromList([
MessageReactionsData( MessageReactionsData(
msg.originId ?? msg.sid, msg.originId ?? msg.sid,
await rs.getReactionsForMessageByJid( await rs.getReactionsForMessageByJid(
command.key.sid, command.id,
command.key.conversationJid,
accountJid, accountJid,
accountJid, accountJid,
), ),
@ -1395,8 +1389,7 @@ Future<void> performGetReactions(
final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid(); final accountJid = await GetIt.I.get<XmppStateService>().getAccountJid();
final reactionsRaw = final reactionsRaw =
await GetIt.I.get<ReactionsService>().getReactionsForMessage( await GetIt.I.get<ReactionsService>().getReactionsForMessage(
command.key.sid, command.id,
command.key.conversationJid,
accountJid, accountJid,
); );
final reactionsMap = <String, List<String>>{}; final reactionsMap = <String, List<String>>{};

View File

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

View File

@ -55,24 +55,29 @@ class FileUploadJob {
@immutable @immutable
class FileDownloadJob { class FileDownloadJob {
const FileDownloadJob( const FileDownloadJob(
this.messageKey, this.messageId,
this.location, this.conversationJid,
this.accountJid, this.accountJid,
this.location,
this.metadataId, this.metadataId,
this.createMetadataHashes, this.createMetadataHashes,
this.mimeGuess, { this.mimeGuess, {
this.shouldShowNotification = true, this.shouldShowNotification = true,
}); });
/// The message key. /// The message id.
final MessageKey messageKey; 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. /// The location where the file can be found.
final MediaFileLocation location; final MediaFileLocation location;
/// The associated account
final String accountJid;
/// The id of the file metadata describing the file. /// The id of the file metadata describing the file.
final String metadataId; final String metadataId;
@ -89,8 +94,9 @@ class FileDownloadJob {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return other is FileDownloadJob && return other is FileDownloadJob &&
messageId == other.messageId &&
conversationJid == other.conversationJid &&
location == other.location && location == other.location &&
messageKey == other.messageKey &&
accountJid == other.accountJid && accountJid == other.accountJid &&
metadataId == other.metadataId && metadataId == other.metadataId &&
mimeGuess == other.mimeGuess && mimeGuess == other.mimeGuess &&
@ -99,8 +105,9 @@ class FileDownloadJob {
@override @override
int get hashCode => int get hashCode =>
conversationJid.hashCode ^
messageId.hashCode ^
location.hashCode ^ location.hashCode ^
messageKey.hashCode ^
metadataId.hashCode ^ metadataId.hashCode ^
mimeGuess.hashCode ^ mimeGuess.hashCode ^
shouldShowNotification.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/file_metadata.dart';
import 'package:moxxyv2/shared/models/message.dart'; import 'package:moxxyv2/shared/models/message.dart';
import 'package:moxxyv2/shared/warning_types.dart'; import 'package:moxxyv2/shared/warning_types.dart';
import 'package:uuid/uuid.dart';
class MessageService { class MessageService {
/// Logger /// Logger
final Logger _log = Logger('MessageService'); final Logger _log = Logger('MessageService');
/// UUID instance for message ids.
final _uuid = const Uuid();
Future<Message> _parseMessage( Future<Message> _parseMessage(
Map<String, Object?> rawMessage, Map<String, Object?> rawMessage,
String accountJid, String accountJid,
@ -46,28 +50,33 @@ class MessageService {
fm, fm,
queryReactionPreview queryReactionPreview
? await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage( ? await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage(
rawMessage['sid']! as String, rawMessage['id']! as String,
rawMessage['conversationJid']! as String,
accountJid, accountJid,
) )
: [], : [],
); );
} }
/// Queries the database for a message with a stanza id of [sid] inside /// Queries the database for a message with a stanza id of [id] inside
/// the conversation [conversationJid] in the context of the account /// the conversation [conversationJid], if specified, in the context of the account
/// [accountJid]. /// [accountJid].
Future<Message?> getMessageBySid( Future<Message?> getMessageById(
String sid, String id,
String conversationJid,
String accountJid, { String accountJid, {
String? conversationJid,
bool queryReactionPreview = true, bool queryReactionPreview = true,
}) async { }) async {
final db = GetIt.I.get<DatabaseService>().database; final db = GetIt.I.get<DatabaseService>().database;
final messagesRaw = await db.query( final messagesRaw = await db.query(
messagesTable, messagesTable,
where: 'sid = ? AND conversationJid = ? AND accountJid = ?', where: conversationJid != null
whereArgs: [sid, conversationJid, accountJid], ? 'id = ? AND accountJid = ? AND conversationJid = ?'
: 'id = ? AND accountJid = ?',
whereArgs: [
id,
accountJid,
if (conversationJid != null) conversationJid,
],
limit: 1, limit: 1,
); );
@ -77,22 +86,53 @@ class MessageService {
} }
/// Queries the database for a message with a stanza id of [originId] inside /// 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]. /// [accountJid].
Future<Message?> getMessageByOriginId( Future<Message?> getMessageByOriginId(
String originId, String originId,
String conversationJid,
String accountJid, { String accountJid, {
String? conversationJid,
bool queryReactionPreview = true, bool queryReactionPreview = true,
}) async { }) async {
final db = GetIt.I.get<DatabaseService>().database; final db = GetIt.I.get<DatabaseService>().database;
final messagesRaw = await db.query( final messagesRaw = await db.query(
messagesTable, messagesTable,
where: 'conversationJid = ? AND accountJid = ? AND originId = ?', where: conversationJid != null
? 'accountJid = ? AND originId = ? AND conversationJid = ?'
: 'accountJid = ? AND originId = ?',
whereArgs: [ whereArgs: [
conversationJid,
accountJid, accountJid,
originId, 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, limit: 1,
); );
@ -135,7 +175,7 @@ SELECT
quote.displayed AS quote_displayed, quote.displayed AS quote_displayed,
quote.acked AS quote_acked, quote.acked AS quote_acked,
quote.originId AS quote_originId, 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.file_metadata_id AS quote_file_metadata_id,
quote.isDownloading AS quote_isDownloading, quote.isDownloading AS quote_isDownloading,
quote.isUploading AS quote_isUploading, quote.isUploading AS quote_isUploading,
@ -162,7 +202,7 @@ SELECT
fm.size as fm_size fm.size as fm_size
FROM (SELECT * FROM $messagesTable WHERE $query ORDER BY timestamp DESC LIMIT $messagePaginationSize) AS msg 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 $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, jid,
@ -178,7 +218,7 @@ FROM (SELECT * FROM $messagesTable WHERE $query ORDER BY timestamp DESC LIMIT $m
} }
Message? quotes; Message? quotes;
if (m['quote_sid'] != null) { if (m['quote_id'] != null) {
final rawQuote = getPrefixedSubMap(m, 'quote_'); final rawQuote = getPrefixedSubMap(m, 'quote_');
FileMetadata? quoteFm; FileMetadata? quoteFm;
@ -209,8 +249,7 @@ FROM (SELECT * FROM $messagesTable WHERE $query ORDER BY timestamp DESC LIMIT $m
quotes, quotes,
fm, fm,
await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage( await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage(
m['sid']! as String, m['id']! as String,
jid,
accountJid, accountJid,
), ),
), ),
@ -296,8 +335,7 @@ FROM
getPrefixedSubMap(m, 'fm_'), getPrefixedSubMap(m, 'fm_'),
), ),
await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage( await GetIt.I.get<ReactionsService>().getPreviewReactionsForMessage(
m['sid']! as String, m['id']! as String,
m['conversationJid']! as String,
accountJid, accountJid,
), ),
), ),
@ -333,6 +371,7 @@ FROM
}) async { }) async {
final db = GetIt.I.get<DatabaseService>().database; final db = GetIt.I.get<DatabaseService>().database;
var message = Message( var message = Message(
_uuid.v4(),
accountJid, accountJid,
sender, sender,
body, body,
@ -358,7 +397,7 @@ FROM
if (quoteId != null) { if (quoteId != null) {
final quotes = final quotes =
await getMessageBySid(quoteId, conversationJid, accountJid); await getMessageById(quoteId, accountJid);
if (quotes == null) { if (quotes == null) {
_log.warning('Failed to add quote for message with id $quoteId'); _log.warning('Failed to add quote for message with id $quoteId');
} else { } else {
@ -372,10 +411,9 @@ FROM
/// Wrapper around [DatabaseService]'s updateMessage that updates the cache /// Wrapper around [DatabaseService]'s updateMessage that updates the cache
Future<Message> updateMessage( Future<Message> updateMessage(
String sid, String id,
String conversationJid,
String accountJid, { String accountJid, {
String? newSid, String? sid,
Object? body = notSpecified, Object? body = notSpecified,
bool? received, bool? received,
bool? displayed, bool? displayed,
@ -432,22 +470,21 @@ FROM
if (isEdited != null) { if (isEdited != null) {
m['isEdited'] = boolToInt(isEdited); m['isEdited'] = boolToInt(isEdited);
} }
if (newSid != null) { if (sid != null) {
m['sid'] = newSid; m['sid'] = sid;
} }
final updatedMessage = await db.updateAndReturn( final updatedMessage = await db.updateAndReturn(
messagesTable, messagesTable,
m, m,
where: 'sid = ? AND conversationJid = ? AND accountJid = ?', where: 'id = ? AND accountJid = ?',
whereArgs: [sid, conversationJid, accountJid], whereArgs: [id, accountJid],
); );
Message? quotes; Message? quotes;
if (updatedMessage['quote_sid'] != null) { if (updatedMessage['quote_id'] != null) {
quotes = await getMessageBySid( quotes = await getMessageById(
updatedMessage['quote_sid']! as String, updatedMessage['quote_id']! as String,
updatedMessage['conversationJid']! as String,
accountJid, accountJid,
queryReactionPreview: false, queryReactionPreview: false,
); );
@ -471,10 +508,9 @@ FROM
updatedMessage, updatedMessage,
quotes, quotes,
metadata, metadata,
// TODO: How should this work with reactions?
await GetIt.I await GetIt.I
.get<ReactionsService>() .get<ReactionsService>()
.getPreviewReactionsForMessage(sid, conversationJid, accountJid), .getPreviewReactionsForMessage(id, accountJid),
); );
return msg; return msg;
@ -500,7 +536,6 @@ FROM
) async { ) async {
final msg = await getMessageByOriginId( final msg = await getMessageByOriginId(
originId, originId,
conversationJid,
accountJid, accountJid,
queryReactionPreview: false, queryReactionPreview: false,
); );
@ -524,8 +559,7 @@ FROM
final isMedia = msg.isMedia; final isMedia = msg.isMedia;
final retractedMessage = await updateMessage( final retractedMessage = await updateMessage(
msg.sid, msg.id,
msg.conversationJid,
accountJid, accountJid,
warningType: null, warningType: null,
errorType: null, errorType: null,
@ -539,7 +573,7 @@ FROM
final conversation = final conversation =
await cs.getConversationByJid(conversationJid, accountJid); await cs.getConversationByJid(conversationJid, accountJid);
if (conversation != null) { if (conversation != null) {
if (conversation.lastMessage?.sid == msg.sid) { if (conversation.lastMessage?.id == msg.id) {
final newConversation = conversation.copyWith( final newConversation = conversation.copyWith(
lastMessage: retractedMessage, 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 /// [MessageUpdatedEvent] to the UI. if [sendChatMarker] is true, then
/// a Chat Marker with <displayed /> is sent to the message's /// a Chat Marker with <displayed /> is sent to the message's
/// conversationJid attribute. /// conversationJid attribute.
Future<Message> markMessageAsRead( Future<Message> markMessageAsRead(
String sid, String id,
String converationJid,
String accountJid, String accountJid,
bool sendChatMarker, bool sendChatMarker,
) async { ) async {
final newMessage = await updateMessage( final newMessage = await updateMessage(
sid, id,
converationJid,
accountJid, accountJid,
displayed: true, displayed: true,
); );

View File

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

View File

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

View File

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

View File

@ -197,7 +197,7 @@ class Conversation with _$Conversation {
'open': boolToInt(open), 'open': boolToInt(open),
'muted': boolToInt(muted), 'muted': boolToInt(muted),
'encrypted': boolToInt(encrypted), '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.freezed.dart';
part 'message.g.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 { enum PseudoMessageType {
/// Indicates that a new device was created in the chat. /// Indicates that a new device was created in the chat.
newDevice(1), newDevice(1),
@ -102,6 +51,9 @@ class PseudoMessageTypeConverter extends JsonConverter<PseudoMessageType, int> {
@freezed @freezed
class Message with _$Message { class Message with _$Message {
factory Message( factory Message(
// The message id (Moxxy-generated UUID).
String id,
/// The JID of the account that sent or received the message. /// The JID of the account that sent or received the message.
String accountJid, String accountJid,
@ -231,7 +183,7 @@ class Message with _$Message {
'encrypted': boolToInt(encrypted), 'encrypted': boolToInt(encrypted),
'file_metadata_id': fileMetadata?.id, 'file_metadata_id': fileMetadata?.id,
// NOTE: Message.quote_id is a foreign-key // NOTE: Message.quote_id is a foreign-key
'quote_sid': quotes?.sid, 'quote_id': quotes?.id,
'isDownloading': boolToInt(isDownloading), 'isDownloading': boolToInt(isDownloading),
'isUploading': boolToInt(isUploading), 'isUploading': boolToInt(isUploading),
'isRetracted': boolToInt(isRetracted), 'isRetracted': boolToInt(isRetracted),
@ -343,12 +295,4 @@ class Message with _$Message {
/// The JID of the sender in moxxmpp's format. /// The JID of the sender in moxxmpp's format.
JID get senderJid => JID.fromString(sender); 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( factory Reaction(
// This is valid in combination with freezed // This is valid in combination with freezed
// ignore: invalid_annotation_target // ignore: invalid_annotation_target
@JsonKey(name: 'message_sid') String messageSid, @JsonKey(name: 'message_id') String messageId,
/// The JID of the conversation this reaction is in.
String conversationJid,
// The account JID of the attached message. // The account JID of the attached message.
String accountJid, 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. // The sender of the reaction.
String senderJid, String senderJid,

View File

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

View File

@ -140,7 +140,7 @@ Future<void> onRosterPush(RosterDiffEvent event, {dynamic extra}) async {
} }
Future<void> onProgress(ProgressEvent 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( Future<void> onSelfAvatarChanged(

View File

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

View File

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

View File

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

View File

@ -1,27 +1,24 @@
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:moxxyv2/shared/models/message.dart';
typedef UIProgressCallback = void Function(double?); typedef UIProgressCallback = void Function(double?);
/// This class handles download progress notifications from the backend and relays them /// This class handles download progress notifications from the backend and relays them
/// to the correct ChatBubble instance so that it can update itself. /// to the correct ChatBubble instance so that it can update itself.
class UIProgressService { class UIProgressService {
UIProgressService() /// Logger.
: _callbacks = {}, final Logger _log = Logger('UIProgressService');
_log = Logger('UIProgressService');
final Logger _log;
// Database message id -> callback function // Database message id -> callback function
final Map<MessageKey, UIProgressCallback> _callbacks; final Map<String, UIProgressCallback> _callbacks = {};
void registerCallback(MessageKey key, UIProgressCallback callback) { void registerCallback(String id, UIProgressCallback callback) {
_log.finest('Registering callback for $key'); _log.finest('Registering callback for $id');
_callbacks[key] = callback; _callbacks[id] = callback;
} }
void unregisterCallback(MessageKey key) { void unregisterCallback(String id) {
_log.finest('Unregistering callback for $key'); _log.finest('Unregistering callback for $id');
_callbacks.remove(key); _callbacks.remove(id);
} }
void unregisterAll() { void unregisterAll() {
@ -29,15 +26,15 @@ class UIProgressService {
_callbacks.clear(); _callbacks.clear();
} }
void onProgress(MessageKey key, double? progress) { void onProgress(String id, double? progress) {
if (_callbacks.containsKey(key)) { if (_callbacks.containsKey(id)) {
if (progress == 1.0) { if (progress == 1.0) {
unregisterCallback(key); unregisterCallback(id);
} else { } else {
_callbacks[key]!(progress); _callbacks[id]!(progress);
} }
} else { } 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'); final Logger _log = Logger('UIReadMarkerService');
/// The cache of messages we already processed. /// 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 /// Checks if we should send a read marker for [message]. If we should, tells
/// the backend to actually send it. /// the backend to actually send it.
void handleMarker(Message message) { void handleMarker(Message message) {
if (_messages.containsKey(message.messageKey)) return; if (_messages.containsKey(message.id)) return;
// Make sure we don't reach here anymore. // 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. // Only send this for messages we have not yet marked as read.
if (message.displayed) return; if (message.displayed) return;
@ -29,12 +29,10 @@ class UIReadMarkerService {
// Check if we should send markers. // Check if we should send markers.
if (!GetIt.I.get<PreferencesBloc>().state.sendChatMarkers) return; if (!GetIt.I.get<PreferencesBloc>().state.sendChatMarkers) return;
final id = message.originId ?? message.sid; _log.finest('Sending chat marker for ${message.id}');
_log.finest('Sending chat marker for ${message.conversationJid}:$id');
MoxplatformPlugin.handler.getDataSender().sendData( MoxplatformPlugin.handler.getDataSender().sendData(
MarkMessageAsReadCommand( MarkMessageAsReadCommand(
sid: message.sid, id: message.id,
conversationJid: message.conversationJid,
sendMarker: true, sendMarker: true,
), ),
awaitable: false, awaitable: false,

View File

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

View File

@ -147,7 +147,7 @@ class FileChatWidget extends StatelessWidget {
maxWidth, maxWidth,
sent, sent,
mimeType: message.fileMetadata!.filename, 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!)), Image.file(File(message.fileMetadata!.path!)),
MessageBubbleBottom(message, sent), MessageBubbleBottom(message, sent),
radius, radius,
extra: ProgressWidget(message.messageKey), extra: ProgressWidget(message.id),
); );
} }
@ -50,7 +50,7 @@ class ImageChatWidget extends StatelessWidget {
), ),
MessageBubbleBottom(message, sent), MessageBubbleBottom(message, sent),
radius, radius,
extra: ProgressWidget(message.messageKey), extra: ProgressWidget(message.id),
); );
} else { } else {
return FileChatBaseWidget( return FileChatBaseWidget(
@ -60,7 +60,7 @@ class ImageChatWidget extends StatelessWidget {
maxWidth, maxWidth,
sent, sent,
mimeType: message.fileMetadata!.mimeType, 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), MessageBubbleBottom(message, sent),
radius, radius,
extra: ProgressWidget(message.messageKey), extra: ProgressWidget(message.id),
); );
} }
@ -61,7 +61,7 @@ class VideoChatWidget extends StatelessWidget {
), ),
MessageBubbleBottom(message, sent), MessageBubbleBottom(message, sent),
radius, radius,
extra: ProgressWidget(message.messageKey), extra: ProgressWidget(message.id),
); );
} else { } else {
return FileChatBaseWidget( return FileChatBaseWidget(
@ -71,7 +71,7 @@ class VideoChatWidget extends StatelessWidget {
maxWidth, maxWidth,
sent, sent,
mimeType: message.fileMetadata!.mimeType, 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:flutter/material.dart';
import 'package:get_it/get_it.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/constants.dart';
import 'package:moxxyv2/ui/service/progress.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 // we, for example, use blurhash, then we compute the image from the blurhash on every
// update. // update.
class ProgressWidget extends StatefulWidget { class ProgressWidget extends StatefulWidget {
const ProgressWidget(this.messageKey, {super.key}); const ProgressWidget(this.messageId, {super.key});
final MessageKey messageKey; final String messageId;
@override @override
ProgressWidgetState createState() => ProgressWidgetState(); ProgressWidgetState createState() => ProgressWidgetState();
@ -31,7 +30,7 @@ class ProgressWidgetState extends State<ProgressWidget> {
// Register against the DownloadService // Register against the DownloadService
GetIt.I GetIt.I
.get<UIProgressService>() .get<UIProgressService>()
.registerCallback(widget.messageKey, _onProgressUpdate); .registerCallback(widget.messageId, _onProgressUpdate);
super.initState(); super.initState();
} }
@ -39,7 +38,7 @@ class ProgressWidgetState extends State<ProgressWidget> {
@override @override
void dispose() { void dispose() {
// Unregister // Unregister
GetIt.I.get<UIProgressService>().unregisterCallback(widget.messageKey); GetIt.I.get<UIProgressService>().unregisterCallback(widget.messageId);
super.dispose(); super.dispose();
} }

View File

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

View File

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