diff --git a/lib/src/database/daos/contacts.dao.dart b/lib/src/database/daos/contacts.dao.dart index 126b03d..d1cff2e 100644 --- a/lib/src/database/daos/contacts.dao.dart +++ b/lib/src/database/daos/contacts.dao.dart @@ -4,6 +4,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly_database_old.dart' as old; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; +import 'package:twonly/src/utils/log.dart'; part 'contacts.dao.g.dart'; @@ -14,10 +15,20 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { // ignore: matching_super_parameters ContactsDao(super.db); - Future insertContact(ContactsCompanion contact) async { + Future insertContact(ContactsCompanion contact) async { try { return await into(contacts).insert(contact); } catch (e) { + Log.error(e); + return null; + } + } + + Future insertOnConflictUpdate(ContactsCompanion contact) async { + try { + return await into(contacts).insertOnConflictUpdate(contact); + } catch (e) { + Log.error(e); return 0; } } @@ -62,10 +73,12 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { Stream> watchNotAcceptedContacts() { return (select(contacts) ..where( - (t) => t.accepted.equals(false) & t.blocked.equals(false), + (t) => + t.accepted.equals(false) & + t.blocked.equals(false) & + t.deletedByUser.equals(false), )) .watch(); - // return (select(contacts)).watch(); } Stream watchContact(int userid) { @@ -74,7 +87,7 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { } Future> getAllNotBlockedContacts() { - return (select(contacts)..where((t) => t.blocked.equals(false))).get(); + return select(contacts).get(); } Stream watchContactsBlocked() { @@ -89,7 +102,9 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { final count = contacts.requested.count(distinct: true); final query = selectOnly(contacts) ..where( - contacts.requested.equals(true) & contacts.accepted.equals(true).not(), + contacts.requested.equals(true) & + contacts.accepted.equals(false) & + contacts.blocked.equals(false), ) ..addColumns([count]); return query.map((row) => row.read(count)).watchSingle(); @@ -113,7 +128,7 @@ String getContactDisplayName(Contact user) { } else if (user.displayName != null) { name = user.displayName!; } - if (user.deleted) { + if (user.accountDeleted) { name = applyStrikethrough(name); } if (name.length > 12) { diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index 6c902e4..09f9fbc 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -81,13 +81,13 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { } Future> getGroupContact(String groupId) async { - final query = select(contacts).join([ + final query = (select(contacts).join([ leftOuterJoin( groupMembers, - groupMembers.contactId.equalsExp(contacts.userId) & - groupMembers.groupId.equals(groupId), + groupMembers.contactId.equalsExp(contacts.userId), ), - ]); + ]) + ..where(groupMembers.groupId.equals(groupId))); return query.map((row) => row.readTable(contacts)).get(); } @@ -101,7 +101,10 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { } Stream> watchGroupsForChatList() { - return (select(groups)..where((t) => t.archived.equals(false))).watch(); + return (select(groups) + ..where((t) => t.archived.equals(false)) + ..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)])) + .watch(); } Future getGroup(String groupId) { @@ -117,7 +120,7 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { u.lastMessageReceived.isNotNull() & u.lastMessageSend.isNotNull(), )) - .watchSingle() + .watchSingleOrNull() .asyncMap(getFlameCounterFromGroup); } @@ -126,14 +129,14 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { } Future getDirectChat(int userId) async { - final query = (select(groups).join([ + final query = + ((select(groups)..where((t) => t.isDirectChat.equals(true))).join([ leftOuterJoin( groupMembers, - groupMembers.groupId.equalsExp(groups.groupId) & - groupMembers.contactId.equals(userId), + groupMembers.groupId.equalsExp(groups.groupId), ), ]) - ..where(groups.isDirectChat.equals(true))); + ..where(groupMembers.contactId.equals(userId))); return query.map((row) => row.readTable(groups)).getSingleOrNull(); } @@ -208,9 +211,23 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { ), ); } + + Future increaseLastMessageExchange( + String groupId, + DateTime newLastMessage, + ) async { + await (update(groups) + ..where( + (t) => + t.groupId.equals(groupId) & + (t.lastMessageExchange.isSmallerThanValue(newLastMessage)), + )) + .write(GroupsCompanion(lastMessageExchange: Value(newLastMessage))); + } } -int getFlameCounterFromGroup(Group group) { +int getFlameCounterFromGroup(Group? group) { + if (group == null) return 0; if (group.lastMessageSend == null || group.lastMessageReceived == null) { return 0; } diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 3189e66..17fefb0 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -32,7 +32,10 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { Stream> watchMessageNotOpened(String groupId) { return (select(messages) - ..where((t) => t.openedAt.isNull() & t.groupId.equals(groupId)) + ..where((t) => + t.openedAt.isNull() & + t.groupId.equals(groupId) & + t.isDeletedFromSender.equals(false)) ..orderBy([(t) => OrderingTerm.desc(t.createdAt)])) .watch(); } @@ -182,7 +185,8 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { Log.error('Message does not exists or contact is not owner.'); return; } - if (msg.mediaId != null) { + if (msg.mediaId != null && contactId != null) { + // contactId -> When a image is send to multiple and one message is delete the image should be still available... await (delete(mediaFiles)..where((t) => t.mediaId.equals(msg.mediaId!))) .go(); @@ -326,8 +330,8 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { Future updateMessageId( String messageId, MessagesCompanion updatedValues, - ) { - return (update(messages)..where((c) => c.messageId.equals(messageId))) + ) async { + await (update(messages)..where((c) => c.messageId.equals(messageId))) .write(updatedValues); } diff --git a/lib/src/database/daos/reactions.dao.dart b/lib/src/database/daos/reactions.dao.dart index e0fa70b..752d6a8 100644 --- a/lib/src/database/daos/reactions.dao.dart +++ b/lib/src/database/daos/reactions.dao.dart @@ -52,6 +52,24 @@ class ReactionsDao extends DatabaseAccessor with _$ReactionsDaoMixin { .watch(); } + Stream watchLastReactions(String groupId) { + final query = (select(reactions) + ..orderBy([(t) => OrderingTerm.desc(t.createdAt)])) + .join( + [ + innerJoin( + messages, + messages.messageId.equalsExp(reactions.messageId), + useColumns: false, + ) + ], + ) + ..where(messages.groupId.equals(groupId)) + // ..orderBy([(t) => OrderingTerm.asc(t.createdAt)])) + ..limit(1); + return query.map((row) => row.readTable(reactions)).watchSingleOrNull(); + } + Stream> watchReactionWithContacts( String messageId, ) { diff --git a/lib/src/database/tables/contacts.table.dart b/lib/src/database/tables/contacts.table.dart index 2fa8096..a427fad 100644 --- a/lib/src/database/tables/contacts.table.dart +++ b/lib/src/database/tables/contacts.table.dart @@ -6,16 +6,19 @@ class Contacts extends Table { TextColumn get username => text()(); TextColumn get displayName => text().nullable()(); TextColumn get nickName => text().nullable()(); - TextColumn get avatarSvg => text().nullable()(); + BlobColumn get avatarSvgCompressed => blob().nullable()(); IntColumn get senderProfileCounter => integer().withDefault(const Constant(0))(); BoolColumn get accepted => boolean().withDefault(const Constant(false))(); + BoolColumn get deletedByUser => + boolean().withDefault(const Constant(false))(); BoolColumn get requested => boolean().withDefault(const Constant(false))(); BoolColumn get blocked => boolean().withDefault(const Constant(false))(); BoolColumn get verified => boolean().withDefault(const Constant(false))(); - BoolColumn get deleted => boolean().withDefault(const Constant(false))(); + BoolColumn get accountDeleted => + boolean().withDefault(const Constant(false))(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index 4ec0de5..95b15a5 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -31,12 +31,12 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { late final GeneratedColumn nickName = GeneratedColumn( 'nick_name', aliasedName, true, type: DriftSqlType.string, requiredDuringInsert: false); - static const VerificationMeta _avatarSvgMeta = - const VerificationMeta('avatarSvg'); + static const VerificationMeta _avatarSvgCompressedMeta = + const VerificationMeta('avatarSvgCompressed'); @override - late final GeneratedColumn avatarSvg = GeneratedColumn( - 'avatar_svg', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn avatarSvgCompressed = + GeneratedColumn('avatar_svg_compressed', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); static const VerificationMeta _senderProfileCounterMeta = const VerificationMeta('senderProfileCounter'); @override @@ -55,6 +55,16 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("accepted" IN (0, 1))'), defaultValue: const Constant(false)); + static const VerificationMeta _deletedByUserMeta = + const VerificationMeta('deletedByUser'); + @override + late final GeneratedColumn deletedByUser = GeneratedColumn( + 'deleted_by_user', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("deleted_by_user" IN (0, 1))'), + defaultValue: const Constant(false)); static const VerificationMeta _requestedMeta = const VerificationMeta('requested'); @override @@ -85,15 +95,15 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("verified" IN (0, 1))'), defaultValue: const Constant(false)); - static const VerificationMeta _deletedMeta = - const VerificationMeta('deleted'); + static const VerificationMeta _accountDeletedMeta = + const VerificationMeta('accountDeleted'); @override - late final GeneratedColumn deleted = GeneratedColumn( - 'deleted', aliasedName, false, + late final GeneratedColumn accountDeleted = GeneratedColumn( + 'account_deleted', aliasedName, false, type: DriftSqlType.bool, requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("deleted" IN (0, 1))'), + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("account_deleted" IN (0, 1))'), defaultValue: const Constant(false)); static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @@ -109,13 +119,14 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { username, displayName, nickName, - avatarSvg, + avatarSvgCompressed, senderProfileCounter, accepted, + deletedByUser, requested, blocked, verified, - deleted, + accountDeleted, createdAt ]; @override @@ -148,9 +159,11 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { context.handle(_nickNameMeta, nickName.isAcceptableOrUnknown(data['nick_name']!, _nickNameMeta)); } - if (data.containsKey('avatar_svg')) { - context.handle(_avatarSvgMeta, - avatarSvg.isAcceptableOrUnknown(data['avatar_svg']!, _avatarSvgMeta)); + if (data.containsKey('avatar_svg_compressed')) { + context.handle( + _avatarSvgCompressedMeta, + avatarSvgCompressed.isAcceptableOrUnknown( + data['avatar_svg_compressed']!, _avatarSvgCompressedMeta)); } if (data.containsKey('sender_profile_counter')) { context.handle( @@ -162,6 +175,12 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { context.handle(_acceptedMeta, accepted.isAcceptableOrUnknown(data['accepted']!, _acceptedMeta)); } + if (data.containsKey('deleted_by_user')) { + context.handle( + _deletedByUserMeta, + deletedByUser.isAcceptableOrUnknown( + data['deleted_by_user']!, _deletedByUserMeta)); + } if (data.containsKey('requested')) { context.handle(_requestedMeta, requested.isAcceptableOrUnknown(data['requested']!, _requestedMeta)); @@ -174,9 +193,11 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { context.handle(_verifiedMeta, verified.isAcceptableOrUnknown(data['verified']!, _verifiedMeta)); } - if (data.containsKey('deleted')) { - context.handle(_deletedMeta, - deleted.isAcceptableOrUnknown(data['deleted']!, _deletedMeta)); + if (data.containsKey('account_deleted')) { + context.handle( + _accountDeletedMeta, + accountDeleted.isAcceptableOrUnknown( + data['account_deleted']!, _accountDeletedMeta)); } if (data.containsKey('created_at')) { context.handle(_createdAtMeta, @@ -199,20 +220,22 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> { .read(DriftSqlType.string, data['${effectivePrefix}display_name']), nickName: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}nick_name']), - avatarSvg: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}avatar_svg']), + avatarSvgCompressed: attachedDatabase.typeMapping.read( + DriftSqlType.blob, data['${effectivePrefix}avatar_svg_compressed']), senderProfileCounter: attachedDatabase.typeMapping.read( DriftSqlType.int, data['${effectivePrefix}sender_profile_counter'])!, accepted: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}accepted'])!, + deletedByUser: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}deleted_by_user'])!, requested: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}requested'])!, blocked: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}blocked'])!, verified: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}verified'])!, - deleted: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}deleted'])!, + accountDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}account_deleted'])!, createdAt: attachedDatabase.typeMapping .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, ); @@ -229,26 +252,28 @@ class Contact extends DataClass implements Insertable { final String username; final String? displayName; final String? nickName; - final String? avatarSvg; + final Uint8List? avatarSvgCompressed; final int senderProfileCounter; final bool accepted; + final bool deletedByUser; final bool requested; final bool blocked; final bool verified; - final bool deleted; + final bool accountDeleted; final DateTime createdAt; const Contact( {required this.userId, required this.username, this.displayName, this.nickName, - this.avatarSvg, + this.avatarSvgCompressed, required this.senderProfileCounter, required this.accepted, + required this.deletedByUser, required this.requested, required this.blocked, required this.verified, - required this.deleted, + required this.accountDeleted, required this.createdAt}); @override Map toColumns(bool nullToAbsent) { @@ -261,15 +286,16 @@ class Contact extends DataClass implements Insertable { if (!nullToAbsent || nickName != null) { map['nick_name'] = Variable(nickName); } - if (!nullToAbsent || avatarSvg != null) { - map['avatar_svg'] = Variable(avatarSvg); + if (!nullToAbsent || avatarSvgCompressed != null) { + map['avatar_svg_compressed'] = Variable(avatarSvgCompressed); } map['sender_profile_counter'] = Variable(senderProfileCounter); map['accepted'] = Variable(accepted); + map['deleted_by_user'] = Variable(deletedByUser); map['requested'] = Variable(requested); map['blocked'] = Variable(blocked); map['verified'] = Variable(verified); - map['deleted'] = Variable(deleted); + map['account_deleted'] = Variable(accountDeleted); map['created_at'] = Variable(createdAt); return map; } @@ -284,15 +310,16 @@ class Contact extends DataClass implements Insertable { nickName: nickName == null && nullToAbsent ? const Value.absent() : Value(nickName), - avatarSvg: avatarSvg == null && nullToAbsent + avatarSvgCompressed: avatarSvgCompressed == null && nullToAbsent ? const Value.absent() - : Value(avatarSvg), + : Value(avatarSvgCompressed), senderProfileCounter: Value(senderProfileCounter), accepted: Value(accepted), + deletedByUser: Value(deletedByUser), requested: Value(requested), blocked: Value(blocked), verified: Value(verified), - deleted: Value(deleted), + accountDeleted: Value(accountDeleted), createdAt: Value(createdAt), ); } @@ -305,14 +332,16 @@ class Contact extends DataClass implements Insertable { username: serializer.fromJson(json['username']), displayName: serializer.fromJson(json['displayName']), nickName: serializer.fromJson(json['nickName']), - avatarSvg: serializer.fromJson(json['avatarSvg']), + avatarSvgCompressed: + serializer.fromJson(json['avatarSvgCompressed']), senderProfileCounter: serializer.fromJson(json['senderProfileCounter']), accepted: serializer.fromJson(json['accepted']), + deletedByUser: serializer.fromJson(json['deletedByUser']), requested: serializer.fromJson(json['requested']), blocked: serializer.fromJson(json['blocked']), verified: serializer.fromJson(json['verified']), - deleted: serializer.fromJson(json['deleted']), + accountDeleted: serializer.fromJson(json['accountDeleted']), createdAt: serializer.fromJson(json['createdAt']), ); } @@ -324,13 +353,14 @@ class Contact extends DataClass implements Insertable { 'username': serializer.toJson(username), 'displayName': serializer.toJson(displayName), 'nickName': serializer.toJson(nickName), - 'avatarSvg': serializer.toJson(avatarSvg), + 'avatarSvgCompressed': serializer.toJson(avatarSvgCompressed), 'senderProfileCounter': serializer.toJson(senderProfileCounter), 'accepted': serializer.toJson(accepted), + 'deletedByUser': serializer.toJson(deletedByUser), 'requested': serializer.toJson(requested), 'blocked': serializer.toJson(blocked), 'verified': serializer.toJson(verified), - 'deleted': serializer.toJson(deleted), + 'accountDeleted': serializer.toJson(accountDeleted), 'createdAt': serializer.toJson(createdAt), }; } @@ -340,26 +370,30 @@ class Contact extends DataClass implements Insertable { String? username, Value displayName = const Value.absent(), Value nickName = const Value.absent(), - Value avatarSvg = const Value.absent(), + Value avatarSvgCompressed = const Value.absent(), int? senderProfileCounter, bool? accepted, + bool? deletedByUser, bool? requested, bool? blocked, bool? verified, - bool? deleted, + bool? accountDeleted, DateTime? createdAt}) => Contact( userId: userId ?? this.userId, username: username ?? this.username, displayName: displayName.present ? displayName.value : this.displayName, nickName: nickName.present ? nickName.value : this.nickName, - avatarSvg: avatarSvg.present ? avatarSvg.value : this.avatarSvg, + avatarSvgCompressed: avatarSvgCompressed.present + ? avatarSvgCompressed.value + : this.avatarSvgCompressed, senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, accepted: accepted ?? this.accepted, + deletedByUser: deletedByUser ?? this.deletedByUser, requested: requested ?? this.requested, blocked: blocked ?? this.blocked, verified: verified ?? this.verified, - deleted: deleted ?? this.deleted, + accountDeleted: accountDeleted ?? this.accountDeleted, createdAt: createdAt ?? this.createdAt, ); Contact copyWithCompanion(ContactsCompanion data) { @@ -369,15 +403,22 @@ class Contact extends DataClass implements Insertable { displayName: data.displayName.present ? data.displayName.value : this.displayName, nickName: data.nickName.present ? data.nickName.value : this.nickName, - avatarSvg: data.avatarSvg.present ? data.avatarSvg.value : this.avatarSvg, + avatarSvgCompressed: data.avatarSvgCompressed.present + ? data.avatarSvgCompressed.value + : this.avatarSvgCompressed, senderProfileCounter: data.senderProfileCounter.present ? data.senderProfileCounter.value : this.senderProfileCounter, accepted: data.accepted.present ? data.accepted.value : this.accepted, + deletedByUser: data.deletedByUser.present + ? data.deletedByUser.value + : this.deletedByUser, requested: data.requested.present ? data.requested.value : this.requested, blocked: data.blocked.present ? data.blocked.value : this.blocked, verified: data.verified.present ? data.verified.value : this.verified, - deleted: data.deleted.present ? data.deleted.value : this.deleted, + accountDeleted: data.accountDeleted.present + ? data.accountDeleted.value + : this.accountDeleted, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -389,13 +430,14 @@ class Contact extends DataClass implements Insertable { ..write('username: $username, ') ..write('displayName: $displayName, ') ..write('nickName: $nickName, ') - ..write('avatarSvg: $avatarSvg, ') + ..write('avatarSvgCompressed: $avatarSvgCompressed, ') ..write('senderProfileCounter: $senderProfileCounter, ') ..write('accepted: $accepted, ') + ..write('deletedByUser: $deletedByUser, ') ..write('requested: $requested, ') ..write('blocked: $blocked, ') ..write('verified: $verified, ') - ..write('deleted: $deleted, ') + ..write('accountDeleted: $accountDeleted, ') ..write('createdAt: $createdAt') ..write(')')) .toString(); @@ -407,13 +449,14 @@ class Contact extends DataClass implements Insertable { username, displayName, nickName, - avatarSvg, + $driftBlobEquality.hash(avatarSvgCompressed), senderProfileCounter, accepted, + deletedByUser, requested, blocked, verified, - deleted, + accountDeleted, createdAt); @override bool operator ==(Object other) => @@ -423,13 +466,15 @@ class Contact extends DataClass implements Insertable { other.username == this.username && other.displayName == this.displayName && other.nickName == this.nickName && - other.avatarSvg == this.avatarSvg && + $driftBlobEquality.equals( + other.avatarSvgCompressed, this.avatarSvgCompressed) && other.senderProfileCounter == this.senderProfileCounter && other.accepted == this.accepted && + other.deletedByUser == this.deletedByUser && other.requested == this.requested && other.blocked == this.blocked && other.verified == this.verified && - other.deleted == this.deleted && + other.accountDeleted == this.accountDeleted && other.createdAt == this.createdAt); } @@ -438,26 +483,28 @@ class ContactsCompanion extends UpdateCompanion { final Value username; final Value displayName; final Value nickName; - final Value avatarSvg; + final Value avatarSvgCompressed; final Value senderProfileCounter; final Value accepted; + final Value deletedByUser; final Value requested; final Value blocked; final Value verified; - final Value deleted; + final Value accountDeleted; final Value createdAt; const ContactsCompanion({ this.userId = const Value.absent(), this.username = const Value.absent(), this.displayName = const Value.absent(), this.nickName = const Value.absent(), - this.avatarSvg = const Value.absent(), + this.avatarSvgCompressed = const Value.absent(), this.senderProfileCounter = const Value.absent(), this.accepted = const Value.absent(), + this.deletedByUser = const Value.absent(), this.requested = const Value.absent(), this.blocked = const Value.absent(), this.verified = const Value.absent(), - this.deleted = const Value.absent(), + this.accountDeleted = const Value.absent(), this.createdAt = const Value.absent(), }); ContactsCompanion.insert({ @@ -465,13 +512,14 @@ class ContactsCompanion extends UpdateCompanion { required String username, this.displayName = const Value.absent(), this.nickName = const Value.absent(), - this.avatarSvg = const Value.absent(), + this.avatarSvgCompressed = const Value.absent(), this.senderProfileCounter = const Value.absent(), this.accepted = const Value.absent(), + this.deletedByUser = const Value.absent(), this.requested = const Value.absent(), this.blocked = const Value.absent(), this.verified = const Value.absent(), - this.deleted = const Value.absent(), + this.accountDeleted = const Value.absent(), this.createdAt = const Value.absent(), }) : username = Value(username); static Insertable custom({ @@ -479,13 +527,14 @@ class ContactsCompanion extends UpdateCompanion { Expression? username, Expression? displayName, Expression? nickName, - Expression? avatarSvg, + Expression? avatarSvgCompressed, Expression? senderProfileCounter, Expression? accepted, + Expression? deletedByUser, Expression? requested, Expression? blocked, Expression? verified, - Expression? deleted, + Expression? accountDeleted, Expression? createdAt, }) { return RawValuesInsertable({ @@ -493,14 +542,16 @@ class ContactsCompanion extends UpdateCompanion { if (username != null) 'username': username, if (displayName != null) 'display_name': displayName, if (nickName != null) 'nick_name': nickName, - if (avatarSvg != null) 'avatar_svg': avatarSvg, + if (avatarSvgCompressed != null) + 'avatar_svg_compressed': avatarSvgCompressed, if (senderProfileCounter != null) 'sender_profile_counter': senderProfileCounter, if (accepted != null) 'accepted': accepted, + if (deletedByUser != null) 'deleted_by_user': deletedByUser, if (requested != null) 'requested': requested, if (blocked != null) 'blocked': blocked, if (verified != null) 'verified': verified, - if (deleted != null) 'deleted': deleted, + if (accountDeleted != null) 'account_deleted': accountDeleted, if (createdAt != null) 'created_at': createdAt, }); } @@ -510,26 +561,28 @@ class ContactsCompanion extends UpdateCompanion { Value? username, Value? displayName, Value? nickName, - Value? avatarSvg, + Value? avatarSvgCompressed, Value? senderProfileCounter, Value? accepted, + Value? deletedByUser, Value? requested, Value? blocked, Value? verified, - Value? deleted, + Value? accountDeleted, Value? createdAt}) { return ContactsCompanion( userId: userId ?? this.userId, username: username ?? this.username, displayName: displayName ?? this.displayName, nickName: nickName ?? this.nickName, - avatarSvg: avatarSvg ?? this.avatarSvg, + avatarSvgCompressed: avatarSvgCompressed ?? this.avatarSvgCompressed, senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, accepted: accepted ?? this.accepted, + deletedByUser: deletedByUser ?? this.deletedByUser, requested: requested ?? this.requested, blocked: blocked ?? this.blocked, verified: verified ?? this.verified, - deleted: deleted ?? this.deleted, + accountDeleted: accountDeleted ?? this.accountDeleted, createdAt: createdAt ?? this.createdAt, ); } @@ -549,8 +602,9 @@ class ContactsCompanion extends UpdateCompanion { if (nickName.present) { map['nick_name'] = Variable(nickName.value); } - if (avatarSvg.present) { - map['avatar_svg'] = Variable(avatarSvg.value); + if (avatarSvgCompressed.present) { + map['avatar_svg_compressed'] = + Variable(avatarSvgCompressed.value); } if (senderProfileCounter.present) { map['sender_profile_counter'] = Variable(senderProfileCounter.value); @@ -558,6 +612,9 @@ class ContactsCompanion extends UpdateCompanion { if (accepted.present) { map['accepted'] = Variable(accepted.value); } + if (deletedByUser.present) { + map['deleted_by_user'] = Variable(deletedByUser.value); + } if (requested.present) { map['requested'] = Variable(requested.value); } @@ -567,8 +624,8 @@ class ContactsCompanion extends UpdateCompanion { if (verified.present) { map['verified'] = Variable(verified.value); } - if (deleted.present) { - map['deleted'] = Variable(deleted.value); + if (accountDeleted.present) { + map['account_deleted'] = Variable(accountDeleted.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -583,13 +640,14 @@ class ContactsCompanion extends UpdateCompanion { ..write('username: $username, ') ..write('displayName: $displayName, ') ..write('nickName: $nickName, ') - ..write('avatarSvg: $avatarSvg, ') + ..write('avatarSvgCompressed: $avatarSvgCompressed, ') ..write('senderProfileCounter: $senderProfileCounter, ') ..write('accepted: $accepted, ') + ..write('deletedByUser: $deletedByUser, ') ..write('requested: $requested, ') ..write('blocked: $blocked, ') ..write('verified: $verified, ') - ..write('deleted: $deleted, ') + ..write('accountDeleted: $accountDeleted, ') ..write('createdAt: $createdAt') ..write(')')) .toString(); @@ -6533,13 +6591,14 @@ typedef $$ContactsTableCreateCompanionBuilder = ContactsCompanion Function({ required String username, Value displayName, Value nickName, - Value avatarSvg, + Value avatarSvgCompressed, Value senderProfileCounter, Value accepted, + Value deletedByUser, Value requested, Value blocked, Value verified, - Value deleted, + Value accountDeleted, Value createdAt, }); typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({ @@ -6547,13 +6606,14 @@ typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({ Value username, Value displayName, Value nickName, - Value avatarSvg, + Value avatarSvgCompressed, Value senderProfileCounter, Value accepted, + Value deletedByUser, Value requested, Value blocked, Value verified, - Value deleted, + Value accountDeleted, Value createdAt, }); @@ -6684,8 +6744,9 @@ class $$ContactsTableFilterComposer ColumnFilters get nickName => $composableBuilder( column: $table.nickName, builder: (column) => ColumnFilters(column)); - ColumnFilters get avatarSvg => $composableBuilder( - column: $table.avatarSvg, builder: (column) => ColumnFilters(column)); + ColumnFilters get avatarSvgCompressed => $composableBuilder( + column: $table.avatarSvgCompressed, + builder: (column) => ColumnFilters(column)); ColumnFilters get senderProfileCounter => $composableBuilder( column: $table.senderProfileCounter, @@ -6694,6 +6755,9 @@ class $$ContactsTableFilterComposer ColumnFilters get accepted => $composableBuilder( column: $table.accepted, builder: (column) => ColumnFilters(column)); + ColumnFilters get deletedByUser => $composableBuilder( + column: $table.deletedByUser, builder: (column) => ColumnFilters(column)); + ColumnFilters get requested => $composableBuilder( column: $table.requested, builder: (column) => ColumnFilters(column)); @@ -6703,8 +6767,9 @@ class $$ContactsTableFilterComposer ColumnFilters get verified => $composableBuilder( column: $table.verified, builder: (column) => ColumnFilters(column)); - ColumnFilters get deleted => $composableBuilder( - column: $table.deleted, builder: (column) => ColumnFilters(column)); + ColumnFilters get accountDeleted => $composableBuilder( + column: $table.accountDeleted, + builder: (column) => ColumnFilters(column)); ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); @@ -6861,8 +6926,9 @@ class $$ContactsTableOrderingComposer ColumnOrderings get nickName => $composableBuilder( column: $table.nickName, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get avatarSvg => $composableBuilder( - column: $table.avatarSvg, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get avatarSvgCompressed => $composableBuilder( + column: $table.avatarSvgCompressed, + builder: (column) => ColumnOrderings(column)); ColumnOrderings get senderProfileCounter => $composableBuilder( column: $table.senderProfileCounter, @@ -6871,6 +6937,10 @@ class $$ContactsTableOrderingComposer ColumnOrderings get accepted => $composableBuilder( column: $table.accepted, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get deletedByUser => $composableBuilder( + column: $table.deletedByUser, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get requested => $composableBuilder( column: $table.requested, builder: (column) => ColumnOrderings(column)); @@ -6880,8 +6950,9 @@ class $$ContactsTableOrderingComposer ColumnOrderings get verified => $composableBuilder( column: $table.verified, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get deleted => $composableBuilder( - column: $table.deleted, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get accountDeleted => $composableBuilder( + column: $table.accountDeleted, + builder: (column) => ColumnOrderings(column)); ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column)); @@ -6908,8 +6979,8 @@ class $$ContactsTableAnnotationComposer GeneratedColumn get nickName => $composableBuilder(column: $table.nickName, builder: (column) => column); - GeneratedColumn get avatarSvg => - $composableBuilder(column: $table.avatarSvg, builder: (column) => column); + GeneratedColumn get avatarSvgCompressed => $composableBuilder( + column: $table.avatarSvgCompressed, builder: (column) => column); GeneratedColumn get senderProfileCounter => $composableBuilder( column: $table.senderProfileCounter, builder: (column) => column); @@ -6917,6 +6988,9 @@ class $$ContactsTableAnnotationComposer GeneratedColumn get accepted => $composableBuilder(column: $table.accepted, builder: (column) => column); + GeneratedColumn get deletedByUser => $composableBuilder( + column: $table.deletedByUser, builder: (column) => column); + GeneratedColumn get requested => $composableBuilder(column: $table.requested, builder: (column) => column); @@ -6926,8 +7000,8 @@ class $$ContactsTableAnnotationComposer GeneratedColumn get verified => $composableBuilder(column: $table.verified, builder: (column) => column); - GeneratedColumn get deleted => - $composableBuilder(column: $table.deleted, builder: (column) => column); + GeneratedColumn get accountDeleted => $composableBuilder( + column: $table.accountDeleted, builder: (column) => column); GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); @@ -7097,13 +7171,14 @@ class $$ContactsTableTableManager extends RootTableManager< Value username = const Value.absent(), Value displayName = const Value.absent(), Value nickName = const Value.absent(), - Value avatarSvg = const Value.absent(), + Value avatarSvgCompressed = const Value.absent(), Value senderProfileCounter = const Value.absent(), Value accepted = const Value.absent(), + Value deletedByUser = const Value.absent(), Value requested = const Value.absent(), Value blocked = const Value.absent(), Value verified = const Value.absent(), - Value deleted = const Value.absent(), + Value accountDeleted = const Value.absent(), Value createdAt = const Value.absent(), }) => ContactsCompanion( @@ -7111,13 +7186,14 @@ class $$ContactsTableTableManager extends RootTableManager< username: username, displayName: displayName, nickName: nickName, - avatarSvg: avatarSvg, + avatarSvgCompressed: avatarSvgCompressed, senderProfileCounter: senderProfileCounter, accepted: accepted, + deletedByUser: deletedByUser, requested: requested, blocked: blocked, verified: verified, - deleted: deleted, + accountDeleted: accountDeleted, createdAt: createdAt, ), createCompanionCallback: ({ @@ -7125,13 +7201,14 @@ class $$ContactsTableTableManager extends RootTableManager< required String username, Value displayName = const Value.absent(), Value nickName = const Value.absent(), - Value avatarSvg = const Value.absent(), + Value avatarSvgCompressed = const Value.absent(), Value senderProfileCounter = const Value.absent(), Value accepted = const Value.absent(), + Value deletedByUser = const Value.absent(), Value requested = const Value.absent(), Value blocked = const Value.absent(), Value verified = const Value.absent(), - Value deleted = const Value.absent(), + Value accountDeleted = const Value.absent(), Value createdAt = const Value.absent(), }) => ContactsCompanion.insert( @@ -7139,13 +7216,14 @@ class $$ContactsTableTableManager extends RootTableManager< username: username, displayName: displayName, nickName: nickName, - avatarSvg: avatarSvg, + avatarSvgCompressed: avatarSvgCompressed, senderProfileCounter: senderProfileCounter, accepted: accepted, + deletedByUser: deletedByUser, requested: requested, blocked: blocked, verified: verified, - deleted: deleted, + accountDeleted: accountDeleted, createdAt: createdAt, ), withReferenceMapper: (p0) => p0 diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index b337e7f..375158c 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -346,6 +346,7 @@ "tabToRemoveEmoji": "Tippen um zu entfernen", "quotedMessageWasDeleted": "Die zitierte Nachricht wurde gelöscht.", "messageWasDeleted": "Nachricht wurde gelöscht.", + "messageWasDeletedShort": "Gelöscht", "sent": "Versendet", "sentTo": "Zugestellt an", "received": "Empfangen", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 3b91d4d..d169e57 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -502,6 +502,7 @@ "tabToRemoveEmoji": "Tab to remove", "quotedMessageWasDeleted": "The quoted message has been deleted.", "messageWasDeleted": "Message has been deleted.", + "messageWasDeletedShort": "Deleted", "sent": "Delivered", "sentTo": "Delivered to", "received": "Received", diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index fac03be..d2f423d 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2114,6 +2114,12 @@ abstract class AppLocalizations { /// **'Message has been deleted.'** String get messageWasDeleted; + /// No description provided for @messageWasDeletedShort. + /// + /// In en, this message translates to: + /// **'Deleted'** + String get messageWasDeletedShort; + /// No description provided for @sent. /// /// In en, this message translates to: diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 653f33f..b4ed1fe 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1123,6 +1123,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get messageWasDeleted => 'Nachricht wurde gelöscht.'; + @override + String get messageWasDeletedShort => 'Gelöscht'; + @override String get sent => 'Versendet'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index e797023..3337bed 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1116,6 +1116,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get messageWasDeleted => 'Message has been deleted.'; + @override + String get messageWasDeletedShort => 'Deleted'; + @override String get sent => 'Delivered'; diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index e777cc5..6a41b3f 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -777,15 +777,15 @@ class EncryptedContent_ContactRequest extends $pb.GeneratedMessage { class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { factory EncryptedContent_ContactUpdate({ EncryptedContent_ContactUpdate_Type? type, - $core.String? avatarSvg, + $core.List<$core.int>? avatarSvgCompressed, $core.String? displayName, }) { final $result = create(); if (type != null) { $result.type = type; } - if (avatarSvg != null) { - $result.avatarSvg = avatarSvg; + if (avatarSvgCompressed != null) { + $result.avatarSvgCompressed = avatarSvgCompressed; } if (displayName != null) { $result.displayName = displayName; @@ -798,7 +798,7 @@ class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.ContactUpdate', createEmptyInstance: create) ..e(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_ContactUpdate_Type.REQUEST, valueOf: EncryptedContent_ContactUpdate_Type.valueOf, enumValues: EncryptedContent_ContactUpdate_Type.values) - ..aOS(2, _omitFieldNames ? '' : 'avatarSvg', protoName: 'avatarSvg') + ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'avatarSvgCompressed', $pb.PbFieldType.OY, protoName: 'avatarSvgCompressed') ..aOS(3, _omitFieldNames ? '' : 'displayName', protoName: 'displayName') ..hasRequiredFields = false ; @@ -834,13 +834,13 @@ class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { void clearType() => clearField(1); @$pb.TagNumber(2) - $core.String get avatarSvg => $_getSZ(1); + $core.List<$core.int> get avatarSvgCompressed => $_getN(1); @$pb.TagNumber(2) - set avatarSvg($core.String v) { $_setString(1, v); } + set avatarSvgCompressed($core.List<$core.int> v) { $_setBytes(1, v); } @$pb.TagNumber(2) - $core.bool hasAvatarSvg() => $_has(1); + $core.bool hasAvatarSvgCompressed() => $_has(1); @$pb.TagNumber(2) - void clearAvatarSvg() => clearField(2); + void clearAvatarSvgCompressed() => clearField(2); @$pb.TagNumber(3) $core.String get displayName => $_getSZ(2); diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index 483604a..43fb535 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -260,12 +260,12 @@ const EncryptedContent_ContactUpdate$json = { '1': 'ContactUpdate', '2': [ {'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedContent.ContactUpdate.Type', '10': 'type'}, - {'1': 'avatarSvg', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'avatarSvg', '17': true}, + {'1': 'avatarSvgCompressed', '3': 2, '4': 1, '5': 12, '9': 0, '10': 'avatarSvgCompressed', '17': true}, {'1': 'displayName', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'displayName', '17': true}, ], '4': [EncryptedContent_ContactUpdate_Type$json], '8': [ - {'1': '_avatarSvg'}, + {'1': '_avatarSvgCompressed'}, {'1': '_displayName'}, ], }; @@ -358,19 +358,20 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( 'ZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEA' 'ASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkK' 'BHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cG' - 'UiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIa0gEKDUNvbnRh' + 'UiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIa8AEKDUNvbnRh' 'Y3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS' - '5UeXBlUgR0eXBlEiEKCWF2YXRhclN2ZxgCIAEoCUgAUglhdmF0YXJTdmeIAQESJQoLZGlzcGxh' - 'eU5hbWUYAyABKAlIAVILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVV' - 'BEQVRFEAFCDAoKX2F2YXRhclN2Z0IOCgxfZGlzcGxheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5' - 'cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SW' - 'QYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgASgMSAFSA2tleYgBARIhCgljcmVhdGVkQXQY' - 'BCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQg' - 'gKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVhdGVkQXQahwEKCUZsYW1lU3luYxIiCgxmbGFtZUNv' - 'dW50ZXIYASABKANSDGZsYW1lQ291bnRlchI2ChZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgAS' - 'gDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh4KCmJlc3RGcmllbmQYAyABKAhSCmJlc3RGcmll' - 'bmRCCgoIX2dyb3VwSWRCDwoNX2lzRGlyZWN0Q2hhdEIXChVfc2VuZGVyUHJvZmlsZUNvdW50ZX' - 'JCEAoOX21lc3NhZ2VVcGRhdGVCCAoGX21lZGlhQg4KDF9tZWRpYVVwZGF0ZUIQCg5fY29udGFj' - 'dFVwZGF0ZUIRCg9fY29udGFjdFJlcXVlc3RCDAoKX2ZsYW1lU3luY0ILCglfcHVzaEtleXNCCw' - 'oJX3JlYWN0aW9uQg4KDF90ZXh0TWVzc2FnZQ=='); + '5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29t' + 'cHJlc3NlZIgBARIlCgtkaXNwbGF5TmFtZRgDIAEoCUgBUgtkaXNwbGF5TmFtZYgBASIfCgRUeX' + 'BlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3NlZEIOCgxf' + 'ZGlzcGxheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW' + '50LlB1c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5' + 'GAMgASgMSAFSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBF' + 'R5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVh' + 'dGVkQXQahwEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1lQ291bnRlch' + 'I2ChZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdl' + 'Eh4KCmJlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmRCCgoIX2dyb3VwSWRCDwoNX2lzRGlyZW' + 'N0Q2hhdEIXChVfc2VuZGVyUHJvZmlsZUNvdW50ZXJCEAoOX21lc3NhZ2VVcGRhdGVCCAoGX21l' + 'ZGlhQg4KDF9tZWRpYVVwZGF0ZUIQCg5fY29udGFjdFVwZGF0ZUIRCg9fY29udGFjdFJlcXVlc3' + 'RCDAoKX2ZsYW1lU3luY0ILCglfcHVzaEtleXNCCwoJX3JlYWN0aW9uQg4KDF90ZXh0TWVzc2Fn' + 'ZQ=='); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index 890ef28..64f947e 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -118,7 +118,7 @@ message EncryptedContent { } Type type = 1; - optional string avatarSvg = 2; + optional bytes avatarSvgCompressed = 2; optional string displayName = 3; } diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 4d6ff9e..71781b7 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -339,7 +339,7 @@ class ApiService { await twonlyDB.contactsDao.updateContact( contactId, ContactsCompanion( - deleted: const Value(true), + accountDeleted: const Value(true), username: Value('${contact.username} (${contact.userId})'), ), ); diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index 22326f0..5e97149 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -52,6 +52,8 @@ Future insertMediaFileInMessagesTable( type: const Value(MessageType.media), ), ); + await twonlyDB.groupsDao + .increaseLastMessageExchange(groupId, DateTime.now()); if (message != null) { // de-archive contact when sending a new message await twonlyDB.groupsDao.updateGroup( diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index aa8a7ac..e748f54 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; import 'package:drift/drift.dart'; import 'package:fixnum/fixnum.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; @@ -116,7 +118,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ await twonlyDB.receiptsDao.deleteReceipt(receiptId); await twonlyDB.contactsDao.updateContact( receipt.contactId, - const ContactsCompanion(deleted: Value(true)), + const ContactsCompanion(accountDeleted: Value(true)), ); return null; } @@ -195,6 +197,8 @@ Future sendCipherTextToGroup( ) async { final groupMembers = await twonlyDB.groupsDao.getGroupMembers(groupId); + await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, DateTime.now()); + encryptedContent.groupId = groupId; for (final groupMember in groupMembers) { @@ -280,7 +284,7 @@ Future notifyContactsAboutProfileChange({int? onlyToContact}) async { final encryptedContent = pb.EncryptedContent( contactUpdate: pb.EncryptedContent_ContactUpdate( type: pb.EncryptedContent_ContactUpdate_Type.UPDATE, - avatarSvg: user.avatarSvg, + avatarSvgCompressed: gzip.encode(utf8.encode(user.avatarSvg!)), displayName: user.displayName, ), ); diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index 9484785..e8e6ad6 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart' hide Message; import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart' as client; @@ -20,6 +21,7 @@ import 'package:twonly/src/services/api/server_messages/reaction.server_message. import 'package:twonly/src/services/api/server_messages/text_message.server_messages.dart'; import 'package:twonly/src/services/signal/encryption.signal.dart'; import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/misc.dart'; final lockHandleServerMessage = Mutex(); @@ -28,19 +30,19 @@ Future handleServerMessage(server.ServerToClient msg) async { final ok = client.Response_Ok()..none = true; var response = client.Response()..ok = ok; - try { - if (msg.v0.hasRequestNewPreKeys()) { - response = await handleRequestNewPreKey(); - } else if (msg.v0.hasNewMessage()) { - final body = Uint8List.fromList(msg.v0.newMessage.body); - final fromUserId = msg.v0.newMessage.fromUserId.toInt(); - await handleNewMessage(fromUserId, body); - } else { - Log.error('Unknown server message: $msg'); - } - } catch (e) { - Log.error(e); + // try { + if (msg.v0.hasRequestNewPreKeys()) { + response = await handleRequestNewPreKey(); + } else if (msg.v0.hasNewMessage()) { + final body = Uint8List.fromList(msg.v0.newMessage.body); + final fromUserId = msg.v0.newMessage.fromUserId.toInt(); + await handleNewMessage(fromUserId, body); + } else { + Log.error('Unknown server message: $msg'); } + // } catch (e) { + // Log.error(e); + // } final v0 = client.V0() ..seq = msg.v0.seq @@ -93,6 +95,21 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { final encryptedContentRaw = Uint8List.fromList(message.encryptedContent); + if (await twonlyDB.contactsDao + .getContactByUserId(fromUserId) + .getSingleOrNull() == + null) { + /// In case the user does not exists, just create a dummy user which was deleted by the user, so the message + /// can be inserted into the receipts database + await twonlyDB.contactsDao.insertContact( + ContactsCompanion( + userId: Value(fromUserId), + deletedByUser: const Value(true), + username: const Value('[deleted]'), + ), + ); + } + final responsePlaintextContent = await handleEncryptedMessage( fromUserId, encryptedContentRaw, @@ -108,6 +125,7 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { } else { response = Message()..type = Message_Type.SENDER_DELIVERY_RECEIPT; } + await twonlyDB.receiptsDao.insertReceipt( ReceiptsCompanion( receiptId: Value(receiptId), @@ -189,8 +207,29 @@ Future handleEncryptedMessage( /// Verify that the user is (still) in that group... if (!await twonlyDB.groupsDao.isContactInGroup(fromUserId, content.groupId)) { - Log.error('User $fromUserId tried to access group ${content.groupId}.'); - return null; + if (getUUIDforDirectChat(gUser.userId, fromUserId) == content.groupId) { + final contact = await twonlyDB.contactsDao + .getContactByUserId(fromUserId) + .getSingleOrNull(); + if (contact == null || contact.deletedByUser) { + Log.error( + 'User tries to send message to direct chat while the user does not exists !', + ); + return null; + } + Log.info( + 'Creating new DirectChat between two users', + ); + await twonlyDB.groupsDao.createNewDirectChat( + fromUserId, + GroupsCompanion( + groupName: Value(getContactDisplayName(contact)), + ), + ); + } else { + Log.error('User $fromUserId tried to access group ${content.groupId}.'); + return null; + } } if (content.hasTextMessage()) { diff --git a/lib/src/services/api/server_messages/contact.server_messages.dart b/lib/src/services/api/server_messages/contact.server_messages.dart index fc96a85..c49ac82 100644 --- a/lib/src/services/api/server_messages/contact.server_messages.dart +++ b/lib/src/services/api/server_messages/contact.server_messages.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart' hide Message; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/messages.dart'; @@ -25,11 +26,12 @@ Future handleContactRequest( if (username.isSuccess) { // ignore: avoid_dynamic_calls final name = username.value.userdata.username as Uint8List; - await twonlyDB.contactsDao.insertContact( + await twonlyDB.contactsDao.insertOnConflictUpdate( ContactsCompanion( username: Value(utf8.decode(name)), userId: Value(fromUserId), requested: const Value(true), + deletedByUser: const Value(false), ), ); } @@ -43,9 +45,25 @@ Future handleContactRequest( accepted: Value(true), ), ); + final contact = await twonlyDB.contactsDao + .getContactByUserId(fromUserId) + .getSingleOrNull(); + await twonlyDB.groupsDao.createNewDirectChat( + fromUserId, + GroupsCompanion( + groupName: Value(getContactDisplayName(contact!)), + ), + ); case EncryptedContent_ContactRequest_Type.REJECT: Log.info('Got a contact reject from $fromUserId'); - await twonlyDB.contactsDao.deleteContactByUserId(fromUserId); + await twonlyDB.contactsDao.updateContact( + fromUserId, + const ContactsCompanion( + accepted: Value(false), + requested: Value(false), + deletedByUser: Value(true), + ), + ); } } @@ -61,13 +79,14 @@ Future handleContactUpdate( case EncryptedContent_ContactUpdate_Type.UPDATE: Log.info('Got a contact update $fromUserId'); - if (contactUpdate.hasAvatarSvg() && + if (contactUpdate.hasAvatarSvgCompressed() && contactUpdate.hasDisplayName() && senderProfileCounter != null) { await twonlyDB.contactsDao.updateContact( fromUserId, ContactsCompanion( - avatarSvg: Value(contactUpdate.avatarSvg), + avatarSvgCompressed: + Value(Uint8List.fromList(contactUpdate.avatarSvgCompressed)), displayName: Value(contactUpdate.displayName), senderProfileCounter: Value(senderProfileCounter), ), diff --git a/lib/src/services/api/server_messages/media.server_messages.dart b/lib/src/services/api/server_messages/media.server_messages.dart index 29afe75..10108b6 100644 --- a/lib/src/services/api/server_messages/media.server_messages.dart +++ b/lib/src/services/api/server_messages/media.server_messages.dart @@ -101,6 +101,8 @@ Future handleMedia( ), ); if (message != null) { + await twonlyDB.groupsDao + .increaseLastMessageExchange(groupId, fromTimestamp(media.timestamp)); Log.info('Inserted a new media message with ID: ${message.messageId}'); await twonlyDB.groupsDao.incFlameCounter( message.groupId, diff --git a/lib/src/services/api/server_messages/text_message.server_messages.dart b/lib/src/services/api/server_messages/text_message.server_messages.dart index dfe6260..f3444cd 100644 --- a/lib/src/services/api/server_messages/text_message.server_messages.dart +++ b/lib/src/services/api/server_messages/text_message.server_messages.dart @@ -29,6 +29,10 @@ Future handleTextMessage( ackByServer: Value(DateTime.now()), ), ); + await twonlyDB.groupsDao.increaseLastMessageExchange( + groupId, + fromTimestamp(textMessage.timestamp), + ); if (message != null) { Log.info('Inserted a new text message with ID: ${message.messageId}'); } diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.dart index c7af9bc..81da8c9 100644 --- a/lib/src/services/api/utils.dart +++ b/lib/src/services/api/utils.dart @@ -12,7 +12,6 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart' hide Message; import 'package:twonly/src/services/api/messages.dart'; -import 'package:twonly/src/services/signal/session.signal.dart'; class Result { Result.error(this.error) : value = null; @@ -57,7 +56,7 @@ ClientToServer createClientToServerFromApplicationData( return ClientToServer()..v0 = v0; } -Future rejectAndDeleteContact(int contactId) async { +Future rejectAndHideContact(int contactId) async { await sendCipherText( contactId, EncryptedContent( @@ -66,9 +65,14 @@ Future rejectAndDeleteContact(int contactId) async { ), ), ); - await twonlyDB.signalDao.deleteAllByContactId(contactId); - await deleteSessionWithTarget(contactId); - await twonlyDB.contactsDao.deleteContactByUserId(contactId); + await twonlyDB.contactsDao.updateContact( + contactId, + const ContactsCompanion( + accepted: Value(false), + requested: Value(false), + deletedByUser: Value(true), + ), + ); } Future handleMediaError(MediaFile media) async { diff --git a/lib/src/services/mediafiles/thumbnail.service.dart b/lib/src/services/mediafiles/thumbnail.service.dart index ffaa999..5af964d 100644 --- a/lib/src/services/mediafiles/thumbnail.service.dart +++ b/lib/src/services/mediafiles/thumbnail.service.dart @@ -9,7 +9,7 @@ Future createThumbnailsForImage( ) async { final fileExtension = sourceFile.path.split('.').last.toLowerCase(); if (fileExtension != 'png') { - Log.error('Could not create thumbnail for image. $fileExtension != .png'); + Log.error('Could not create thumbnail for image. $fileExtension != png'); return; } diff --git a/lib/src/services/notifications/setup.notifications.dart b/lib/src/services/notifications/setup.notifications.dart index 03ff634..6d9b3b0 100644 --- a/lib/src/services/notifications/setup.notifications.dart +++ b/lib/src/services/notifications/setup.notifications.dart @@ -7,6 +7,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_svg/svg.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/utils/misc.dart'; final StreamController selectNotificationStream = StreamController.broadcast(); @@ -61,10 +62,11 @@ Future createPushAvatars() async { final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts(); for (final contact in contacts) { - if (contact.avatarSvg == null) return; + if (contact.avatarSvgCompressed == null) return; - final pictureInfo = - await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null); + final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!); + + final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null); final image = await pictureInfo.picture.toImage(300, 300); diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index b2adc93..d86d788 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -372,3 +374,8 @@ String friendlyDateTime( return '$timePart $datePart'; } + +String getAvatarSvg(Uint8List avatarSvgCompressed) { + final raw = gzip.decode(avatarSvgCompressed); + return utf8.decode(raw); +} diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index dedf439..5bdf57d 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -12,7 +12,6 @@ import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/headline.dart'; @@ -49,8 +48,7 @@ class _SearchUsernameView extends State { } Future _addNewUser(BuildContext context) async { - final user = await getUser(); - if (user == null || user.username == searchUserName.text || !mounted) { + if (gUser.username == searchUserName.text) { return; } @@ -84,11 +82,13 @@ class _SearchUsernameView extends State { return; } - final added = await twonlyDB.contactsDao.insertContact( + final added = await twonlyDB.contactsDao.insertOnConflictUpdate( ContactsCompanion( username: Value(searchUserName.text), userId: Value(userdata.userId.toInt()), requested: const Value(false), + blocked: const Value(false), + deletedByUser: const Value(false), ), ); @@ -223,18 +223,26 @@ class ContactsListView extends StatelessWidget { child: IconButton( icon: const Icon(Icons.close, color: Colors.red), onPressed: () async { - await rejectAndDeleteContact(contact.userId); + await rejectAndHideContact(contact.userId); }, ), ), IconButton( icon: const Icon(Icons.check, color: Colors.green), onPressed: () async { - const update = ContactsCompanion( - accepted: Value(true), - requested: Value(false), + await twonlyDB.contactsDao.updateContact( + contact.userId, + const ContactsCompanion( + accepted: Value(true), + requested: Value(false), + ), + ); + await twonlyDB.groupsDao.createNewDirectChat( + contact.userId, + GroupsCompanion( + groupName: Value(getContactDisplayName(contact)), + ), ); - await twonlyDB.contactsDao.updateContact(contact.userId, update); await sendCipherText( contact.userId, EncryptedContent( diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index f13fac1..e2491ae 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -47,7 +47,7 @@ class _ChatListViewState extends State { } Future initAsync() async { - final stream = twonlyDB.groupsDao.watchGroups(); + final stream = twonlyDB.groupsDao.watchGroupsForChatList(); _contactsSub = stream.listen((groups) { setState(() { _groupsNotPinned = groups.where((x) => !x.pinned).toList(); diff --git a/lib/src/views/chats/chat_list_components/group_list_item.dart b/lib/src/views/chats/chat_list_components/group_list_item.dart index dd4bab5..03fba1b 100644 --- a/lib/src/views/chats/chat_list_components/group_list_item.dart +++ b/lib/src/views/chats/chat_list_components/group_list_item.dart @@ -38,7 +38,9 @@ class _UserListItem extends State { late StreamSubscription> messagesNotOpenedStream; Message? lastMessage; + Reaction? lastReaction; late StreamSubscription lastMessageStream; + late StreamSubscription lastReactionStream; late StreamSubscription> lastMediaFilesStream; List previewMessages = []; @@ -54,6 +56,7 @@ class _UserListItem extends State { @override void dispose() { messagesNotOpenedStream.cancel(); + lastReactionStream.cancel(); lastMessageStream.cancel(); lastMediaFilesStream.cancel(); super.dispose(); @@ -68,6 +71,17 @@ class _UserListItem extends State { }); }); + lastReactionStream = twonlyDB.reactionsDao + .watchLastReactions(widget.group.groupId) + .listen((update) { + setState(() { + lastReaction = update; + }); + // protectUpdateState.protect(() async { + // await updateState(lastMessage, update, messagesNotOpened); + // }); + }); + messagesNotOpenedStream = twonlyDB.messagesDao .watchMessageNotOpened(widget.group.groupId) .listen((update) { @@ -141,9 +155,7 @@ class _UserListItem extends State { lastMessage = newLastMessage; messagesNotOpened = newMessagesNotOpened; - setState(() { - // sets lastMessages, messagesNotOpened and currentMessage - }); + if (mounted) setState(() {}); } Future onTap() async { @@ -217,7 +229,11 @@ class _UserListItem extends State { ? Text(context.lang.chatsTapToSend) : Row( children: [ - MessageSendStateIcon(previewMessages, previewMediaFiles), + MessageSendStateIcon( + previewMessages, + previewMediaFiles, + lastReaction: lastReaction, + ), const Text('•'), const SizedBox(width: 5), if (currentMessage != null) diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart index 858a3cc..e4b9a19 100644 --- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart +++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart @@ -179,7 +179,7 @@ class _ChatListEntryState extends State { Message? nextMessage, bool hasReactions, ) { - var bottom = 30.0; + var bottom = 20.0; var top = 0.0; var topLeft = 12.0; @@ -189,7 +189,7 @@ class _ChatListEntryState extends State { if (nextMessage != null) { if (message.senderId == nextMessage.senderId) { - bottom = 10; + bottom = 3; } } @@ -203,7 +203,7 @@ class _ChatListEntryState extends State { final combinesWidthNext = combineTextMessageWithNext(message, nextMessage); if (combinesWidthNext) { - bottom = 1; + bottom = 0; bottomLeft = 5.0; } diff --git a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart index 2cfe578..f8abbed 100644 --- a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart +++ b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart @@ -5,6 +5,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; @@ -44,10 +45,12 @@ class MessageSendStateIcon extends StatefulWidget { this.mediaFiles, { super.key, this.canBeReopened = false, + this.lastReaction, this.mainAxisAlignment = MainAxisAlignment.end, }); final List messages; final List mediaFiles; + final Reaction? lastReaction; final MainAxisAlignment mainAxisAlignment; final bool canBeReopened; @@ -125,8 +128,8 @@ class _MessageSendStateIconState extends State { case MessageSendState.received: icon = Icon(Icons.square_rounded, size: 14, color: color); text = context.lang.messageSendState_Received; - if (message.type == MessageType.media) { - if (mediaFile!.downloadState == DownloadState.pending) { + if (message.type == MessageType.media && mediaFile != null) { + if (mediaFile.downloadState == DownloadState.pending) { text = context.lang.messageSendState_TapToLoad; } if (mediaFile.downloadState == DownloadState.downloading) { @@ -170,6 +173,11 @@ class _MessageSendStateIconState extends State { } } + if (message.isDeletedFromSender) { + icon = FaIcon(FontAwesomeIcons.trash, size: 10, color: color); + text = context.lang.messageWasDeletedShort; + } + if (hasLoader) { icons = [icon]; break; @@ -182,6 +190,41 @@ class _MessageSendStateIconState extends State { } } + if (widget.lastReaction != null && + !widget.messages.any((t) => t.openedAt == null)) { + /// No messages are still open, so check if the reaction is the last message received. + + if (!widget.messages + .any((m) => m.createdAt.isAfter(widget.lastReaction!.createdAt))) { + if (EmojiAnimation.animatedIcons + .containsKey(widget.lastReaction!.emoji)) { + icons = [ + SizedBox( + height: 18, + child: EmojiAnimation(emoji: widget.lastReaction!.emoji), + ) + ]; + } else { + icons = [ + SizedBox( + height: 18, + child: Center( + child: Text( + widget.lastReaction!.emoji, + style: const TextStyle(fontSize: 18), + strutStyle: const StrutStyle( + forceStrutHeight: true, + height: 1.6, + ), + ), + ), + ) + ]; + } + // Log.info("DISPLAY REACTION"); + } + } + if (icons.isEmpty) return Container(); var icon = icons[0]; diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/views/components/avatar_icon.component.dart index 8e9706e..c05a9f6 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/views/components/avatar_icon.component.dart @@ -4,6 +4,7 @@ import 'package:twonly/globals.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/misc.dart'; class AvatarIcon extends StatefulWidget { const AvatarIcon({ @@ -38,21 +39,21 @@ class _AvatarIconState extends State { final contacts = await twonlyDB.groupsDao.getGroupContact(widget.group!.groupId); if (contacts.length == 1) { - if (contacts.first.avatarSvg != null) { - avatarSVGs.add(contacts.first.avatarSvg!); + if (contacts.first.avatarSvgCompressed != null) { + avatarSVGs.add(getAvatarSvg(contacts.first.avatarSvgCompressed!)); } } else { for (final contact in contacts) { - if (contact.avatarSvg != null) { - avatarSVGs.add(contact.avatarSvg!); + if (contact.avatarSvgCompressed != null) { + avatarSVGs.add(getAvatarSvg(contact.avatarSvgCompressed!)); } } } // avatarSvg = group!.avatarSvg; } else if (widget.userData?.avatarSvg != null) { avatarSVGs.add(widget.userData!.avatarSvg!); - } else if (widget.contact?.avatarSvg != null) { - avatarSVGs.add(widget.contact!.avatarSvg!); + } else if (widget.contact?.avatarSvgCompressed != null) { + avatarSVGs.add(getAvatarSvg(widget.contact!.avatarSvgCompressed!)); } if (mounted) setState(() {}); } diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index 7b51f36..5ff2d8e 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -30,7 +30,7 @@ class _ContactViewState extends State { ); if (remove) { // trigger deletion for the other user... - await rejectAndDeleteContact(contact.userId); + await rejectAndHideContact(contact.userId); if (mounted) { Navigator.popUntil(context, (route) => route.isFirst); } diff --git a/lib/src/views/updates/62_database_migration.view.dart b/lib/src/views/updates/62_database_migration.view.dart index a0e53a3..15821cb 100644 --- a/lib/src/views/updates/62_database_migration.view.dart +++ b/lib/src/views/updates/62_database_migration.view.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; @@ -39,134 +40,139 @@ class _DatabaseMigrationViewState extends State { final oldMessages = await oldDatabase.messages.select().get(); for (final oldContact in oldContacts) { + if (oldContact.deleted) continue; + Uint8List? avatarSvg; + if (oldContact.avatarSvg != null) { + avatarSvg = + Uint8List.fromList(gzip.encode(utf8.encode(oldContact.avatarSvg!))); + } await twonlyDB.contactsDao.insertContact( ContactsCompanion( userId: Value(oldContact.userId), username: Value(oldContact.username), displayName: Value(oldContact.displayName), nickName: Value(oldContact.nickName), - avatarSvg: Value(oldContact.avatarSvg), + avatarSvgCompressed: Value(avatarSvg), senderProfileCounter: const Value(0), accepted: Value(oldContact.accepted), requested: Value(oldContact.requested), blocked: Value(oldContact.blocked), verified: Value(oldContact.verified), - deleted: Value(oldContact.deleted), createdAt: Value(oldContact.createdAt), ), ); setState(() { _contactsMigrated += 1; }); - if (!oldContact.deleted) { - final group = await twonlyDB.groupsDao.createNewDirectChat( - oldContact.userId, - GroupsCompanion( - pinned: Value(oldContact.pinned), - archived: Value(oldContact.archived), - groupName: Value(getContactDisplayNameOld(oldContact)), - totalMediaCounter: Value(oldContact.totalMediaCounter), - alsoBestFriend: Value(oldContact.alsoBestFriend), - createdAt: Value(oldContact.createdAt), - lastFlameCounterChange: Value(oldContact.lastFlameCounterChange), - lastFlameSync: Value(oldContact.lastFlameSync), - lastMessageExchange: Value(oldContact.lastMessageExchange), - lastMessageReceived: Value(oldContact.lastMessageReceived), - lastMessageSend: Value(oldContact.lastMessageSend), - flameCounter: Value(oldContact.flameCounter), + final group = await twonlyDB.groupsDao.createNewDirectChat( + oldContact.userId, + GroupsCompanion( + pinned: Value(oldContact.pinned), + archived: Value(oldContact.archived), + groupName: Value(getContactDisplayNameOld(oldContact)), + totalMediaCounter: Value(oldContact.totalMediaCounter), + alsoBestFriend: Value(oldContact.alsoBestFriend), + createdAt: Value(oldContact.createdAt), + lastFlameCounterChange: Value(oldContact.lastFlameCounterChange), + lastFlameSync: Value(oldContact.lastFlameSync), + lastMessageExchange: Value(oldContact.lastMessageExchange), + lastMessageReceived: Value(oldContact.lastMessageReceived), + lastMessageSend: Value(oldContact.lastMessageSend), + flameCounter: Value(oldContact.flameCounter), + ), + ); + if (group == null) continue; + for (final oldMessage in oldMessages) { + if (oldMessage.mediaUploadId == null && + oldMessage.mediaDownloadId == null) { + /// only interested in media files... + continue; + } + if (oldMessage.contactId != oldContact.userId) continue; + if (!oldMessage.mediaStored) continue; + + var storedMediaPath = + join((await getApplicationSupportDirectory()).path, 'media'); + if (oldMessage.mediaDownloadId != null) { + storedMediaPath = + '${join(storedMediaPath, 'received')}/${oldMessage.mediaDownloadId}'; + } else { + storedMediaPath = + '${join(storedMediaPath, 'send')}/${oldMessage.mediaDownloadId}'; + } + + var type = MediaType.image; + if (File('$storedMediaPath.mp4').existsSync()) { + type = MediaType.video; + storedMediaPath = '$storedMediaPath.mp4'; + } else if (File('$storedMediaPath.png').existsSync()) { + type = MediaType.image; + storedMediaPath = '$storedMediaPath.png'; + } else if (File('$storedMediaPath.webp').existsSync()) { + type = MediaType.image; + storedMediaPath = '$storedMediaPath.webp'; + } else { + continue; + } + + final uniqueId = Value( + getUUIDforDirectChat( + oldMessage.messageOtherId ?? oldMessage.messageId, + oldMessage.contactId ^ gUser.userId, ), ); - if (group == null) continue; - for (final oldMessage in oldMessages) { - if (oldMessage.mediaUploadId == null && - oldMessage.mediaDownloadId == null) { - /// only interested in media files... - continue; - } - if (oldMessage.contactId != oldContact.userId) continue; - if (!oldMessage.mediaStored) continue; - var storedMediaPath = - join((await getApplicationSupportDirectory()).path, 'media'); - if (oldMessage.mediaDownloadId != null) { - storedMediaPath = - '${join(storedMediaPath, 'received')}/${oldMessage.mediaDownloadId}'; - } else { - storedMediaPath = - '${join(storedMediaPath, 'send')}/${oldMessage.mediaDownloadId}'; - } + final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( + MediaFilesCompanion( + mediaId: uniqueId, + stored: const Value(true), + type: Value(type), + createdAt: Value(oldMessage.sendAt), + ), + ); + if (mediaFile == null) continue; - var type = MediaType.image; - if (File('$storedMediaPath.mp4').existsSync()) { - type = MediaType.video; - storedMediaPath = '$storedMediaPath.mp4'; - } else if (File('$storedMediaPath.png').existsSync()) { - type = MediaType.image; - storedMediaPath = '$storedMediaPath.png'; - } else if (File('$storedMediaPath.webp').existsSync()) { - type = MediaType.image; - storedMediaPath = '$storedMediaPath.webp'; - } else { - continue; - } + final message = await twonlyDB.messagesDao.insertMessage( + MessagesCompanion( + messageId: uniqueId, + groupId: Value(group.groupId), + mediaId: uniqueId, + type: const Value(MessageType.media), + ), + ); + if (message == null) continue; - final uniqueId = Value( - getUUIDforDirectChat( - oldMessage.messageOtherId ?? oldMessage.messageId, - oldMessage.contactId ^ gUser.userId, - ), - ); - - final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( - MediaFilesCompanion( - mediaId: uniqueId, - stored: const Value(true), - type: Value(type), - createdAt: Value(oldMessage.sendAt), - ), - ); - if (mediaFile == null) continue; - - final message = await twonlyDB.messagesDao.insertMessage( - MessagesCompanion( - messageId: uniqueId, - groupId: Value(group.groupId), - mediaId: uniqueId, - type: const Value(MessageType.media), - ), - ); - if (message == null) continue; - - final mediaService = await MediaFileService.fromMedia(mediaFile); - File(storedMediaPath).copySync(mediaService.storedPath.path); - setState(() { - _storedMediaFiles += 1; - }); - } + final mediaService = await MediaFileService.fromMedia(mediaFile); + File(storedMediaPath).copySync(mediaService.storedPath.path); + setState(() { + _storedMediaFiles += 1; + }); } } final memoriesPath = Directory( join((await getApplicationSupportDirectory()).path, 'media', 'memories'), ); - final files = memoriesPath.listSync(); - for (final file in files) { - if (file.path.contains('thumbnail')) continue; - final type = - file.path.contains('mp4') ? MediaType.video : MediaType.image; - final stat = FileStat.statSync(file.path); - final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( - MediaFilesCompanion( - type: Value(type), - createdAt: Value(stat.modified), - stored: const Value(true), - ), - ); - final mediaService = await MediaFileService.fromMedia(mediaFile!); - File(file.path).copySync(mediaService.storedPath.path); - setState(() { - _storedMediaFiles += 1; - }); + if (memoriesPath.existsSync()) { + final files = memoriesPath.listSync(); + for (final file in files) { + if (file.path.contains('thumbnail')) continue; + final type = + file.path.contains('mp4') ? MediaType.video : MediaType.image; + final stat = FileStat.statSync(file.path); + final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( + MediaFilesCompanion( + type: Value(type), + createdAt: Value(stat.modified), + stored: const Value(true), + ), + ); + final mediaService = await MediaFileService.fromMedia(mediaFile!); + File(file.path).copySync(mediaService.storedPath.path); + setState(() { + _storedMediaFiles += 1; + }); + } } final oldContactPreKeys =