multiple reactions possible and quoted message size improved

This commit is contained in:
otsmr 2025-10-29 09:32:07 +01:00
parent 37790aa304
commit f2bd80c2dc
11 changed files with 161 additions and 110 deletions

View file

@ -4,6 +4,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/reactions.table.dart'; import 'package:twonly/src/database/tables/reactions.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
part 'reactions.dao.g.dart'; part 'reactions.dao.g.dart';
@ -18,21 +19,29 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
int contactId, int contactId,
String messageId, String messageId,
String groupId, String groupId,
String? emoji, String emoji,
bool remove,
) async { ) async {
if (!isEmoji(emoji)) {
Log.error('Did not update reaction as it is not an emoji!');
return;
}
final msg = final msg =
await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull(); await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull();
if (msg == null || msg.groupId != groupId) return; if (msg == null || msg.groupId != groupId) return;
try { try {
if (remove) {
await (delete(reactions) await (delete(reactions)
..where( ..where(
(t) => (t) =>
t.senderId.equals(contactId) & t.messageId.equals(messageId), t.senderId.equals(contactId) &
t.messageId.equals(messageId) &
t.emoji.equals(emoji),
)) ))
.go(); .go();
if (emoji != null) { } else {
await into(reactions).insert( await into(reactions).insertOnConflictUpdate(
ReactionsCompanion( ReactionsCompanion(
messageId: Value(messageId), messageId: Value(messageId),
emoji: Value(emoji), emoji: Value(emoji),
@ -45,6 +54,42 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
} }
} }
Future<void> updateMyReaction(
String messageId,
String emoji,
bool remove,
) async {
if (!isEmoji(emoji)) {
Log.error('Did not update reaction as it is not an emoji!');
return;
}
final msg =
await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull();
if (msg == null) return;
try {
await (delete(reactions)
..where(
(t) =>
t.senderId.isNull() &
t.messageId.equals(messageId) &
t.emoji.equals(emoji),
))
.go();
if (!remove) {
await into(reactions).insert(
ReactionsCompanion(
messageId: Value(messageId),
emoji: Value(emoji),
senderId: const Value(null),
),
);
}
} catch (e) {
Log.error(e);
}
}
Stream<List<Reaction>> watchReactions(String messageId) { Stream<List<Reaction>> watchReactions(String messageId) {
return (select(reactions) return (select(reactions)
..where((t) => t.messageId.equals(messageId)) ..where((t) => t.messageId.equals(messageId))
@ -81,25 +126,4 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
.map((row) => (row.readTable(reactions), row.readTableOrNull(contacts))) .map((row) => (row.readTable(reactions), row.readTableOrNull(contacts)))
.watch(); .watch();
} }
Future<void> updateMyReaction(String messageId, String? emoji) async {
try {
await (delete(reactions)
..where(
(t) => t.senderId.isNull() & t.messageId.equals(messageId),
))
.go();
if (emoji != null) {
await into(reactions).insert(
ReactionsCompanion(
messageId: Value(messageId),
emoji: Value(emoji),
senderId: const Value(null),
),
);
}
} catch (e) {
Log.error(e);
}
}
} }

View file

@ -143,12 +143,8 @@ const EncryptedContent_Reaction$json = {
'1': 'Reaction', '1': 'Reaction',
'2': [ '2': [
{'1': 'targetMessageId', '3': 1, '4': 1, '5': 9, '10': 'targetMessageId'}, {'1': 'targetMessageId', '3': 1, '4': 1, '5': 9, '10': 'targetMessageId'},
{'1': 'emoji', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'emoji', '17': true}, {'1': 'emoji', '3': 2, '4': 1, '5': 9, '10': 'emoji'},
{'1': 'remove', '3': 3, '4': 1, '5': 8, '9': 1, '10': 'remove', '17': true}, {'1': 'remove', '3': 3, '4': 1, '5': 8, '10': 'remove'},
],
'8': [
{'1': '_emoji'},
{'1': '_remove'},
], ],
}; };
@ -333,45 +329,44 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'50ZW50LlRleHRNZXNzYWdlSAtSC3RleHRNZXNzYWdliAEBGqkBCgtUZXh0TWVzc2FnZRIoCg9z' '50ZW50LlRleHRNZXNzYWdlSAtSC3RleHRNZXNzYWdliAEBGqkBCgtUZXh0TWVzc2FnZRIoCg9z'
'ZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZX' 'ZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZX'
'h0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAQgASgJ' 'h0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAQgASgJ'
'SABSDnF1b3RlTWVzc2FnZUlkiAEBQhEKD19xdW90ZU1lc3NhZ2VJZBqBAQoIUmVhY3Rpb24SKA' 'SABSDnF1b3RlTWVzc2FnZUlkiAEBQhEKD19xdW90ZU1lc3NhZ2VJZBpiCghSZWFjdGlvbhIoCg'
'oPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSGQoFZW1vamkYAiABKAlI' '90YXJnZXRNZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIF'
'AFIFZW1vammIAQESGwoGcmVtb3ZlGAMgASgISAFSBnJlbW92ZYgBAUIICgZfZW1vamlCCQoHX3' 'ZW1vamkSFgoGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZR'
'JlbW92ZRq3AgoNTWVzc2FnZVVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVu' 'gBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3Nl'
'dC5NZXNzYWdlVXBkYXRlLlR5cGVSBHR5cGUSLQoPc2VuZGVyTWVzc2FnZUlkGAIgASgJSABSD3' 'bmRlck1lc3NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYX'
'NlbmRlck1lc3NhZ2VJZIgBARI6ChhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMYAyADKAlSGG11' 'JnZXRNZXNzYWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgE'
'bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxIXCgR0ZXh0GAQgASgJSAFSBHRleHSIAQESHAoJdGltZX' 'IAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCg'
'N0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlwZRIKCgZERUxFVEUQABINCglFRElUX1RFWFQQ' 'oGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJ'
'ARIKCgZPUEVORUQQAkISChBfc2VuZGVyTWVzc2FnZUlkQgcKBV90ZXh0GowFCgVNZWRpYRIoCg' 'ZEIHCgVfdGV4dBqMBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZX'
'9zZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGAIgASgOMhwu' 'NzYWdlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlw'
'RW5jcnlwdGVkQ29udGVudC5NZWRpYS5UeXBlUgR0eXBlEkMKGmRpc3BsYXlMaW1pdEluTWlsbG' 'ZRJDChpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk'
'lzZWNvbmRzGAMgASgDSABSGmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEjYKFnJlcXVp' '1pbGxpc2Vjb25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJl'
'cmVzQXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZX' 'c0F1dGhlbnRpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTW'
'N0YW1wGAUgASgDUgl0aW1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBiABKAlIAVIOcXVvdGVN' 'Vzc2FnZUlkGAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByAB'
'ZXNzYWdlSWSIAQESKQoNZG93bmxvYWRUb2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEi' 'KAxIAlINZG93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cH'
'kKDWVuY3J5cHRpb25LZXkYCCABKAxIA1INZW5jcnlwdGlvbktleYgBARIpCg1lbmNyeXB0aW9u' 'Rpb25LZXmIAQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0K'
'TWFjGAkgASgMSARSDWVuY3J5cHRpb25NYWOIAQESLQoPZW5jcnlwdGlvbk5vbmNlGAogASgMSA' 'D2VuY3J5cHRpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiMwoEVHlwZRIMCg'
'VSD2VuY3J5cHRpb25Ob25jZYgBASIzCgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJ' 'hSRVVQTE9BRBAAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQA0IdChtfZGlzcGxheUxp'
'CgVWSURFTxACEgcKA0dJRhADQh0KG19kaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kc0IRCg9fcX' 'bWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQh'
'VvdGVNZXNzYWdlSWRCEAoOX2Rvd25sb2FkVG9rZW5CEAoOX2VuY3J5cHRpb25LZXlCEAoOX2Vu' 'AKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2Ua'
'Y3J5cHRpb25NYWNCEgoQX2VuY3J5cHRpb25Ob25jZRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZR' 'pwEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVX'
'gBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJn' 'BkYXRlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdl'
'ZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEA' 'SWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1'
'ASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkK' 'IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5D'
'BHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cG' 'b250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVB'
'UiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIa8AEKDUNvbnRh' 'ABEgoKBkFDQ0VQVBACGvABCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0'
'Y3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS' 'ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRI1ChNhdmF0YXJTdmdDb21wcmVzc2'
'5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29t' 'VkGAIgASgMSABSE2F2YXRhclN2Z0NvbXByZXNzZWSIAQESJQoLZGlzcGxheU5hbWUYAyABKAlI'
'cHJlc3NlZIgBARIlCgtkaXNwbGF5TmFtZRgDIAEoCUgBUgtkaXNwbGF5TmFtZYgBASIfCgRUeX' 'AVILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCFgoUX2'
'BlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3NlZEIOCgxf' 'F2YXRhclN2Z0NvbXByZXNzZWRCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBl'
'ZGlzcGxheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW' 'GAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGA'
'50LlB1c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5' 'IgASgDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQg'
'GAMgASgMSAFSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBF' 'ASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICg'
'R5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVh' 'Zfa2V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GocBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3Vu'
'dGVkQXQahwEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1lQ291bnRlch' 'dGVyGAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1'
'I2ChZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdl' 'IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5k'
'Eh4KCmJlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmRCCgoIX2dyb3VwSWRCDwoNX2lzRGlyZW' 'QgoKCF9ncm91cElkQg8KDV9pc0RpcmVjdENoYXRCFwoVX3NlbmRlclByb2ZpbGVDb3VudGVyQh'
'N0Q2hhdEIXChVfc2VuZGVyUHJvZmlsZUNvdW50ZXJCEAoOX21lc3NhZ2VVcGRhdGVCCAoGX21l' 'AKDl9tZXNzYWdlVXBkYXRlQggKBl9tZWRpYUIOCgxfbWVkaWFVcGRhdGVCEAoOX2NvbnRhY3RV'
'ZGlhQg4KDF9tZWRpYVVwZGF0ZUIQCg5fY29udGFjdFVwZGF0ZUIRCg9fY29udGFjdFJlcXVlc3' 'cGRhdGVCEQoPX2NvbnRhY3RSZXF1ZXN0QgwKCl9mbGFtZVN5bmNCCwoJX3B1c2hLZXlzQgsKCV'
'RCDAoKX2ZsYW1lU3luY0ILCglfcHVzaEtleXNCCwoJX3JlYWN0aW9uQg4KDF90ZXh0TWVzc2Fn' '9yZWFjdGlvbkIOCgxfdGV4dE1lc3NhZ2U=');
'ZQ==');

View file

@ -54,8 +54,8 @@ message EncryptedContent {
message Reaction { message Reaction {
string targetMessageId = 1; string targetMessageId = 1;
optional string emoji = 2; string emoji = 2;
optional bool remove = 3; bool remove = 3;
} }
message MessageUpdate { message MessageUpdate {

View file

@ -7,21 +7,16 @@ Future<void> handleReaction(
String groupId, String groupId,
EncryptedContent_Reaction reaction, EncryptedContent_Reaction reaction,
) async { ) async {
Log.info('Got a reaction from $fromUserId'); Log.info('Got a reaction from $fromUserId (remove=${reaction.remove})');
if (reaction.hasRemove()) {
if (reaction.remove) {
await twonlyDB.reactionsDao
.updateReaction(fromUserId, reaction.targetMessageId, groupId, null);
return;
}
}
if (reaction.hasEmoji()) {
await twonlyDB.reactionsDao.updateReaction( await twonlyDB.reactionsDao.updateReaction(
fromUserId, fromUserId,
reaction.targetMessageId, reaction.targetMessageId,
groupId, groupId,
reaction.emoji, reaction.emoji,
reaction.remove,
); );
if (!reaction.remove) {
await twonlyDB.groupsDao await twonlyDB.groupsDao
.increaseLastMessageExchange(groupId, DateTime.now()); .increaseLastMessageExchange(groupId, DateTime.now());
} }

View file

@ -8,6 +8,7 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart';
class AllReactionsView extends StatefulWidget { class AllReactionsView extends StatefulWidget {
@ -47,14 +48,18 @@ class _AllReactionsViewState extends State<AllReactionsView> {
setState(() {}); setState(() {});
} }
Future<void> removeReaction() async { Future<void> removeReaction(String emoji) async {
await twonlyDB.reactionsDao await twonlyDB.reactionsDao.updateMyReaction(
.updateMyReaction(widget.message.messageId, null); widget.message.messageId,
emoji,
true,
);
await sendCipherTextToGroup( await sendCipherTextToGroup(
widget.message.groupId, widget.message.groupId,
pb.EncryptedContent( pb.EncryptedContent(
reaction: pb.EncryptedContent_Reaction( reaction: pb.EncryptedContent_Reaction(
targetMessageId: widget.message.messageId, targetMessageId: widget.message.messageId,
emoji: emoji,
remove: true, remove: true,
), ),
), ),
@ -97,12 +102,17 @@ class _AllReactionsViewState extends State<AllReactionsView> {
child: ListView( child: ListView(
children: reactionsUsers.map((entry) { children: reactionsUsers.map((entry) {
return GestureDetector( return GestureDetector(
onTap: (entry.$2 != null) ? null : removeReaction, onTap: (entry.$2 != null)
? null
: () {
removeReaction(entry.$1.emoji);
},
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 5, vertical: 5,
horizontal: 30, horizontal: 30,
), ),
color: Colors.transparent,
margin: const EdgeInsets.only(left: 4), margin: const EdgeInsets.only(left: 4),
child: Row( child: Row(
children: [ children: [
@ -130,10 +140,30 @@ class _AllReactionsViewState extends State<AllReactionsView> {
], ],
), ),
), ),
Text( if (EmojiAnimation.animatedIcons
.containsKey(entry.$1.emoji))
SizedBox(
height: 25,
child: EmojiAnimation(emoji: entry.$1.emoji),
)
else
SizedBox(
height: 24,
child: Center(
child: Text(
entry.$1.emoji, entry.$1.emoji,
style: const TextStyle(fontSize: 25), style: const TextStyle(fontSize: 22),
strutStyle: const StrutStyle(
forceStrutHeight: true,
height: 1.6,
), ),
),
),
),
// Text(
// entry.$1.emoji,
// style: const TextStyle(fontSize: 25),
// ),
], ],
), ),
), ),

View file

@ -133,6 +133,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
group: widget.group, group: widget.group,
mediaService: mediaService!, mediaService: mediaService!,
galleryItems: widget.galleryItems, galleryItems: widget.galleryItems,
minWidth: reactionsForWidth * 43,
), ),
), ),
if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10), if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10),

View file

@ -22,10 +22,12 @@ class ChatMediaEntry extends StatefulWidget {
required this.group, required this.group,
required this.galleryItems, required this.galleryItems,
required this.mediaService, required this.mediaService,
required this.minWidth,
super.key, super.key,
}); });
final Message message; final Message message;
final double minWidth;
final Group group; final Group group;
final List<MemoryItem> galleryItems; final List<MemoryItem> galleryItems;
final MediaFileService mediaService; final MediaFileService mediaService;
@ -117,7 +119,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
onDoubleTap: onDoubleTap, onDoubleTap: onDoubleTap,
onTap: (widget.message.type == MessageType.media) ? onTap : null, onTap: (widget.message.type == MessageType.media) ? onTap : null,
child: SizedBox( child: SizedBox(
width: 150, width: (widget.minWidth > 150) ? widget.minWidth : 150,
height: (widget.message.mediaStored && height: (widget.message.mediaStored &&
widget.mediaService.imagePreviewAvailable) widget.mediaService.imagePreviewAvailable)
? 271 ? 271

View file

@ -113,9 +113,10 @@ class ReactionRow extends StatelessWidget {
child: Text( child: Text(
entry.$2.toString(), entry.$2.toString(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
color: Colors.black, color:
isDarkMode(context) ? Colors.white : Colors.black,
decoration: TextDecoration.none, decoration: TextDecoration.none,
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),

View file

@ -53,8 +53,7 @@ class ChatTextEntry extends StatelessWidget {
if (message.isDeletedFromSender) { if (message.isDeletedFromSender) {
color = context.color.surfaceBright; color = context.color.surfaceBright;
displayTime = false; displayTime = false;
} else if (measureTextWidth(text) > 270 || } else if (measureTextWidth(text) > 270) {
message.quotesMessageId != null) {
expanded = true; expanded = true;
} }

View file

@ -50,8 +50,11 @@ class MessageContextMenu extends StatelessWidget {
) as EmojiLayerData?; ) as EmojiLayerData?;
if (layer == null) return; if (layer == null) return;
await twonlyDB.reactionsDao await twonlyDB.reactionsDao.updateMyReaction(
.updateMyReaction(message.messageId, layer.text); message.messageId,
layer.text,
false,
);
await sendCipherTextToGroup( await sendCipherTextToGroup(
message.groupId, message.groupId,

View file

@ -36,7 +36,7 @@ class _EmojiReactionWidgetState extends State<EmojiReactionWidget> {
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
await twonlyDB.reactionsDao await twonlyDB.reactionsDao
.updateMyReaction(widget.messageId, widget.emoji); .updateMyReaction(widget.messageId, widget.emoji, false);
await sendCipherTextToGroup( await sendCipherTextToGroup(
widget.groupId, widget.groupId,
@ -44,6 +44,7 @@ class _EmojiReactionWidgetState extends State<EmojiReactionWidget> {
reaction: EncryptedContent_Reaction( reaction: EncryptedContent_Reaction(
targetMessageId: widget.messageId, targetMessageId: widget.messageId,
emoji: widget.emoji, emoji: widget.emoji,
remove: false,
), ),
), ),
null, null,