feat(service): Handle message retraction
This commit is contained in:
parent
ef108f2e4a
commit
2dd9847566
@ -51,6 +51,7 @@ Future<void> createDatabase(Database db, int version) async {
|
||||
isDownloading INTEGER NOT NULL,
|
||||
isUploading INTEGER NOT NULL,
|
||||
mediaSize INTEGER,
|
||||
isRetracted INTEGER,
|
||||
CONSTRAINT fk_quote FOREIGN KEY (quote_id) REFERENCES $messsagesTable (id)
|
||||
)''',
|
||||
);
|
||||
|
@ -9,7 +9,9 @@ import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:moxxyv2/service/database/creation.dart';
|
||||
import 'package:moxxyv2/service/database/helpers.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_language.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_retraction.dart';
|
||||
import 'package:moxxyv2/service/database/migrations/0000_xmpp_state.dart';
|
||||
import 'package:moxxyv2/service/not_specified.dart';
|
||||
import 'package:moxxyv2/service/omemo/omemo.dart';
|
||||
import 'package:moxxyv2/service/roster.dart';
|
||||
import 'package:moxxyv2/service/state.dart';
|
||||
@ -26,7 +28,6 @@ import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
const databasePasswordKey = 'database_encryption_password';
|
||||
|
||||
class DatabaseService {
|
||||
|
||||
DatabaseService() : _log = Logger('DatabaseService');
|
||||
late Database _db;
|
||||
final FlutterSecureStorage _storage = const FlutterSecureStorage(
|
||||
@ -55,7 +56,7 @@ class DatabaseService {
|
||||
_db = await openDatabase(
|
||||
dbPath,
|
||||
password: key,
|
||||
version: 3,
|
||||
version: 4,
|
||||
onCreate: createDatabase,
|
||||
onConfigure: configureDatabase,
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
@ -67,6 +68,10 @@ class DatabaseService {
|
||||
_log.finest('Running migration for database version 3');
|
||||
await upgradeFromV2ToV3(db);
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
_log.finest('Running migration for database version 4');
|
||||
await upgradeFromV3ToV4(db);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -357,28 +362,45 @@ class DatabaseService {
|
||||
final msg = messagesRaw.first;
|
||||
return Message.fromDatabaseJson(msg, null);
|
||||
}
|
||||
|
||||
|
||||
Future<Message?> getMessageByOriginId(String id, String conversationJid) async {
|
||||
final messagesRaw = await _db.query(
|
||||
'Messages',
|
||||
where: 'conversationJid = ? AND originId = ?',
|
||||
whereArgs: [conversationJid, id],
|
||||
limit: 1,
|
||||
);
|
||||
|
||||
if (messagesRaw.isEmpty) return null;
|
||||
|
||||
// TODO(PapaTutuWawa): Load the quoted message
|
||||
final msg = messagesRaw.first;
|
||||
return Message.fromDatabaseJson(msg, null);
|
||||
}
|
||||
|
||||
/// Updates the message item with id [id] inside the database.
|
||||
Future<Message> updateMessage(int id, {
|
||||
String? mediaUrl,
|
||||
String? mediaType,
|
||||
Object? body = notSpecified,
|
||||
Object? mediaUrl = notSpecified,
|
||||
Object? mediaType = notSpecified,
|
||||
bool? received,
|
||||
bool? displayed,
|
||||
bool? acked,
|
||||
int? errorType,
|
||||
int? warningType,
|
||||
Object? errorType = notSpecified,
|
||||
Object? warningType = notSpecified,
|
||||
bool? isFileUploadNotification,
|
||||
String? srcUrl,
|
||||
String? key,
|
||||
String? iv,
|
||||
String? encryptionScheme,
|
||||
int? mediaWidth,
|
||||
int? mediaHeight,
|
||||
Object? srcUrl = notSpecified,
|
||||
Object? key = notSpecified,
|
||||
Object? iv = notSpecified,
|
||||
Object? encryptionScheme = notSpecified,
|
||||
Object? mediaWidth = notSpecified,
|
||||
Object? mediaHeight = notSpecified,
|
||||
bool? isDownloading,
|
||||
bool? isUploading,
|
||||
int? mediaSize,
|
||||
String? originId,
|
||||
String? sid,
|
||||
Object? mediaSize = notSpecified,
|
||||
Object? originId = notSpecified,
|
||||
Object? sid = notSpecified,
|
||||
bool? isRetracted,
|
||||
}) async {
|
||||
final md = (await _db.query(
|
||||
'Messages',
|
||||
@ -388,11 +410,11 @@ class DatabaseService {
|
||||
)).first;
|
||||
final m = Map<String, dynamic>.from(md);
|
||||
|
||||
if (mediaUrl != null) {
|
||||
m['mediaUrl'] = mediaUrl;
|
||||
if (mediaUrl != notSpecified) {
|
||||
m['mediaUrl'] = mediaUrl as String?;
|
||||
}
|
||||
if (mediaType != null) {
|
||||
m['mediaType'] = mediaType;
|
||||
if (mediaType != notSpecified) {
|
||||
m['mediaType'] = mediaType as String?;
|
||||
}
|
||||
if (received != null) {
|
||||
m['received'] = boolToInt(received);
|
||||
@ -403,35 +425,35 @@ class DatabaseService {
|
||||
if (acked != null) {
|
||||
m['acked'] = boolToInt(acked);
|
||||
}
|
||||
if (errorType != null) {
|
||||
m['errorType'] = errorType;
|
||||
if (errorType != notSpecified) {
|
||||
m['errorType'] = errorType as int?;
|
||||
}
|
||||
if (warningType != null) {
|
||||
m['warningType'] = warningType;
|
||||
if (warningType != notSpecified) {
|
||||
m['warningType'] = warningType as int?;
|
||||
}
|
||||
if (isFileUploadNotification != null) {
|
||||
m['isFileUploadNotification'] = boolToInt(isFileUploadNotification);
|
||||
}
|
||||
if (srcUrl != null) {
|
||||
m['srcUrl'] = srcUrl;
|
||||
if (srcUrl != notSpecified) {
|
||||
m['srcUrl'] = srcUrl as String?;
|
||||
}
|
||||
if (mediaWidth != null) {
|
||||
m['mediaWidth'] = mediaWidth;
|
||||
if (mediaWidth != notSpecified) {
|
||||
m['mediaWidth'] = mediaWidth as int?;
|
||||
}
|
||||
if (mediaHeight != null) {
|
||||
m['mediaHeight'] = mediaHeight;
|
||||
if (mediaHeight != notSpecified) {
|
||||
m['mediaHeight'] = mediaHeight as int?;
|
||||
}
|
||||
if (mediaSize != null) {
|
||||
m['mediaSize'] = mediaSize;
|
||||
if (mediaSize != notSpecified) {
|
||||
m['mediaSize'] = mediaSize as int?;
|
||||
}
|
||||
if (key != null) {
|
||||
m['key'] = key;
|
||||
if (key != notSpecified) {
|
||||
m['key'] = key as String?;
|
||||
}
|
||||
if (iv != null) {
|
||||
m['iv'] = iv;
|
||||
if (iv != notSpecified) {
|
||||
m['iv'] = iv as String?;
|
||||
}
|
||||
if (encryptionScheme != null) {
|
||||
m['encryptionScheme'] = encryptionScheme;
|
||||
if (encryptionScheme != notSpecified) {
|
||||
m['encryptionScheme'] = encryptionScheme as String?;
|
||||
}
|
||||
if (isDownloading != null) {
|
||||
m['isDownloading'] = boolToInt(isDownloading);
|
||||
@ -439,11 +461,14 @@ class DatabaseService {
|
||||
if (isUploading != null) {
|
||||
m['isUploading'] = boolToInt(isUploading);
|
||||
}
|
||||
if (sid != null) {
|
||||
m['sid'] = sid;
|
||||
if (sid != notSpecified) {
|
||||
m['sid'] = sid as String?;
|
||||
}
|
||||
if (originId != null) {
|
||||
m['originId'] = originId;
|
||||
if (originId != notSpecified) {
|
||||
m['originId'] = originId as String?;
|
||||
}
|
||||
if (isRetracted != null) {
|
||||
m['isRetracted'] = boolToInt(isRetracted);
|
||||
}
|
||||
|
||||
await _db.update(
|
||||
|
11
lib/service/database/migrations/0000_retraction.dart
Normal file
11
lib/service/database/migrations/0000_retraction.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:moxxyv2/service/database/constants.dart';
|
||||
import 'package:moxxyv2/service/database/helpers.dart';
|
||||
import 'package:moxxyv2/shared/models/preference.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
|
||||
Future<void> upgradeFromV3ToV4(Database db) async {
|
||||
// Mark all messages as not retracted
|
||||
await db.execute(
|
||||
'ALTER TABLE $messsagesTable ADD COLUMN isRetracted INTEGER DEFAULT ${boolToInt(false)};',
|
||||
);
|
||||
}
|
@ -3,6 +3,7 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxyv2/service/database/database.dart';
|
||||
import 'package:moxxyv2/service/not_specified.dart';
|
||||
import 'package:moxxyv2/shared/models/message.dart';
|
||||
|
||||
class MessageService {
|
||||
@ -120,28 +121,31 @@ class MessageService {
|
||||
|
||||
/// Wrapper around [DatabaseService]'s updateMessage that updates the cache
|
||||
Future<Message> updateMessage(int id, {
|
||||
String? mediaUrl,
|
||||
String? mediaType,
|
||||
Object? body = notSpecified,
|
||||
Object? mediaUrl = notSpecified,
|
||||
Object? mediaType = notSpecified,
|
||||
bool? received,
|
||||
bool? displayed,
|
||||
bool? acked,
|
||||
int? errorType,
|
||||
int? warningType,
|
||||
Object? errorType = notSpecified,
|
||||
Object? warningType = notSpecified,
|
||||
bool? isFileUploadNotification,
|
||||
String? srcUrl,
|
||||
String? key,
|
||||
String? iv,
|
||||
String? encryptionScheme,
|
||||
int? mediaWidth,
|
||||
int? mediaHeight,
|
||||
int? mediaSize,
|
||||
Object? srcUrl = notSpecified,
|
||||
Object? key = notSpecified,
|
||||
Object? iv = notSpecified,
|
||||
Object? encryptionScheme = notSpecified,
|
||||
Object? mediaWidth = notSpecified,
|
||||
Object? mediaHeight = notSpecified,
|
||||
Object? mediaSize = notSpecified,
|
||||
bool? isUploading,
|
||||
bool? isDownloading,
|
||||
String? originId,
|
||||
String? sid,
|
||||
Object? originId = notSpecified,
|
||||
Object? sid = notSpecified,
|
||||
bool? isRetracted,
|
||||
}) async {
|
||||
final newMessage = await GetIt.I.get<DatabaseService>().updateMessage(
|
||||
id,
|
||||
body: body,
|
||||
mediaUrl: mediaUrl,
|
||||
mediaType: mediaType,
|
||||
received: received,
|
||||
@ -161,6 +165,7 @@ class MessageService {
|
||||
isDownloading: isDownloading,
|
||||
originId: originId,
|
||||
sid: sid,
|
||||
isRetracted: isRetracted,
|
||||
);
|
||||
|
||||
if (_messageCache.containsKey(newMessage.conversationJid)) {
|
||||
|
4
lib/service/not_specified.dart
Normal file
4
lib/service/not_specified.dart
Normal file
@ -0,0 +1,4 @@
|
||||
class _NotSpecifiedValue { const _NotSpecifiedValue(); }
|
||||
|
||||
/// A value used for indicating that a value is not specified.
|
||||
const notSpecified = _NotSpecifiedValue();
|
@ -198,6 +198,7 @@ Future<void> entrypoint() async {
|
||||
EmeManager(),
|
||||
CryptographicHashManager(),
|
||||
DelayedDeliveryManager(),
|
||||
MessageRetractionManager(),
|
||||
])
|
||||
..registerFeatureNegotiators([
|
||||
ResourceBindingNegotiator(),
|
||||
|
@ -740,6 +740,37 @@ class XmppService {
|
||||
&& implies(event.oob != null, event.body == event.oob?.url);
|
||||
}
|
||||
|
||||
/// Handle a message retraction given the MessageEvent [event].
|
||||
Future<void> _handleMessageRetraction(MessageEvent event, String conversationJid) async {
|
||||
final msg = await GetIt.I.get<DatabaseService>().getMessageByOriginId(
|
||||
event.messageRetraction!.id,
|
||||
conversationJid,
|
||||
);
|
||||
|
||||
if (msg == null) {
|
||||
_log.finest('Got message retraction for origin Id ${event.messageRetraction!.id}, but did not find the message');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(PapaTutuWawa): Change the lastMessageBody of the conversation if that message was retracted
|
||||
final retractedMessage = await GetIt.I.get<MessageService>().updateMessage(
|
||||
msg.id,
|
||||
mediaUrl: null,
|
||||
mediaType: null,
|
||||
warningType: null,
|
||||
errorType: null,
|
||||
srcUrl: null,
|
||||
key: null,
|
||||
iv: null,
|
||||
encryptionScheme: null,
|
||||
mediaWidth: null,
|
||||
mediaHeight: null,
|
||||
mediaSize: null,
|
||||
isRetracted: true,
|
||||
);
|
||||
sendEvent(MessageUpdatedEvent(message: retractedMessage));
|
||||
}
|
||||
|
||||
/// Returns true if a file should be automatically downloaded. If it should not, it
|
||||
/// returns false.
|
||||
/// [conversationJid] refers to the JID of the conversation the message was received in.
|
||||
@ -763,6 +794,11 @@ class XmppService {
|
||||
await _handleFileUploadNotificationReplacement(event, conversationJid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.messageRetraction != null) {
|
||||
await _handleMessageRetraction(event, conversationJid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the processing here if the event does not describe a displayable message
|
||||
if (!_isMessageEventMessage(event) && event.other['encryption_error'] == null) return;
|
||||
|
@ -53,6 +53,7 @@ class Message with _$Message {
|
||||
@Default(false) bool received,
|
||||
@Default(false) bool displayed,
|
||||
@Default(false) bool acked,
|
||||
@Default(false) bool isRetracted,
|
||||
String? originId,
|
||||
Message? quotes,
|
||||
String? filename,
|
||||
@ -80,6 +81,7 @@ class Message with _$Message {
|
||||
'ciphertextHashes': _optionalJsonDecode(json['ciphertextHashes'] as String?),
|
||||
'isDownloading': intToBool(json['isDownloading']! as int),
|
||||
'isUploading': intToBool(json['isUploading']! as int),
|
||||
'isRetracted': intToBool(json['isRetracted']! as int),
|
||||
}).copyWith(quotes: quotes);
|
||||
}
|
||||
|
||||
@ -102,6 +104,7 @@ class Message with _$Message {
|
||||
'ciphertextHashes': _optionalJsonEncode(ciphertextHashes),
|
||||
'isDownloading': boolToInt(isDownloading),
|
||||
'isUploading': boolToInt(isUploading),
|
||||
'isRetracted': boolToInt(isRetracted),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,18 @@ class TextChatWidget extends StatelessWidget {
|
||||
final bool sent;
|
||||
final Widget? topWidget;
|
||||
|
||||
String getMessageText() {
|
||||
if (message.isError()) {
|
||||
return errorTypeToText(message.errorType!);
|
||||
}
|
||||
|
||||
if (message.isRetracted) {
|
||||
return 'RETRACTED';
|
||||
}
|
||||
|
||||
return message.body;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fontsize = EmojiUtil.hasOnlyEmojis(
|
||||
@ -50,11 +62,9 @@ class TextChatWidget extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: ParsedText(
|
||||
text: message.isError() ?
|
||||
errorTypeToText(message.errorType!) :
|
||||
message.body,
|
||||
text: getMessageText(),
|
||||
style: TextStyle(
|
||||
color: message.isError() ?
|
||||
color: message.isError() || message.isRetracted ?
|
||||
Colors.grey :
|
||||
const Color(0xf9ebffff),
|
||||
fontSize: fontsize,
|
||||
|
@ -750,9 +750,9 @@ packages:
|
||||
moxxmpp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: moxxmpp
|
||||
url: "https://git.polynom.me/api/packages/Moxxy/pub/"
|
||||
source: hosted
|
||||
path: "../moxxmpp/packages/moxxmpp"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.3+1"
|
||||
moxxmpp_socket_tcp:
|
||||
dependency: "direct main"
|
||||
|
@ -127,6 +127,8 @@ dependency_overrides:
|
||||
git:
|
||||
url: https://codeberg.org/PapaTutuWawa/omemo_dart.git
|
||||
rev: c68471349ab1b347ec9ad54651265710842c50b7
|
||||
moxxmpp:
|
||||
path: ../moxxmpp/packages/moxxmpp
|
||||
|
||||
extra_licenses:
|
||||
- name: undraw.co
|
||||
|
Loading…
Reference in New Issue
Block a user