display last reaction and compress avatarsvgs

This commit is contained in:
otsmr 2025-10-27 20:14:07 +01:00
parent 3d5fc3e807
commit 350fb899e1
33 changed files with 604 additions and 297 deletions

View file

@ -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<TwonlyDB> with _$ContactsDaoMixin {
// ignore: matching_super_parameters
ContactsDao(super.db);
Future<int> insertContact(ContactsCompanion contact) async {
Future<int?> insertContact(ContactsCompanion contact) async {
try {
return await into(contacts).insert(contact);
} catch (e) {
Log.error(e);
return null;
}
}
Future<int> 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<TwonlyDB> with _$ContactsDaoMixin {
Stream<List<Contact>> 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<Contact?> watchContact(int userid) {
@ -74,7 +87,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
}
Future<List<Contact>> getAllNotBlockedContacts() {
return (select(contacts)..where((t) => t.blocked.equals(false))).get();
return select(contacts).get();
}
Stream<int?> watchContactsBlocked() {
@ -89,7 +102,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> 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) {

View file

@ -81,13 +81,13 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
}
Future<List<Contact>> 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<TwonlyDB> with _$GroupsDaoMixin {
}
Stream<List<Group>> 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<Group?> getGroup(String groupId) {
@ -117,7 +120,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
u.lastMessageReceived.isNotNull() &
u.lastMessageSend.isNotNull(),
))
.watchSingle()
.watchSingleOrNull()
.asyncMap(getFlameCounterFromGroup);
}
@ -126,14 +129,14 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
}
Future<Group?> 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<TwonlyDB> with _$GroupsDaoMixin {
),
);
}
Future<void> 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;
}

View file

@ -32,7 +32,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
Stream<List<Message>> 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<TwonlyDB> 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<TwonlyDB> with _$MessagesDaoMixin {
Future<void> 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);
}

View file

@ -52,6 +52,24 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
.watch();
}
Stream<Reaction?> 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<List<(Reaction, Contact?)>> watchReactionWithContacts(
String messageId,
) {

View file

@ -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)();

View file

@ -31,12 +31,12 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
late final GeneratedColumn<String> nickName = GeneratedColumn<String>(
'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<String> avatarSvg = GeneratedColumn<String>(
'avatar_svg', aliasedName, true,
type: DriftSqlType.string, requiredDuringInsert: false);
late final GeneratedColumn<Uint8List> avatarSvgCompressed =
GeneratedColumn<Uint8List>('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<bool> deletedByUser = GeneratedColumn<bool>(
'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<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, false,
late final GeneratedColumn<bool> accountDeleted = GeneratedColumn<bool>(
'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<Contact> {
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<String, Expression> toColumns(bool nullToAbsent) {
@ -261,15 +286,16 @@ class Contact extends DataClass implements Insertable<Contact> {
if (!nullToAbsent || nickName != null) {
map['nick_name'] = Variable<String>(nickName);
}
if (!nullToAbsent || avatarSvg != null) {
map['avatar_svg'] = Variable<String>(avatarSvg);
if (!nullToAbsent || avatarSvgCompressed != null) {
map['avatar_svg_compressed'] = Variable<Uint8List>(avatarSvgCompressed);
}
map['sender_profile_counter'] = Variable<int>(senderProfileCounter);
map['accepted'] = Variable<bool>(accepted);
map['deleted_by_user'] = Variable<bool>(deletedByUser);
map['requested'] = Variable<bool>(requested);
map['blocked'] = Variable<bool>(blocked);
map['verified'] = Variable<bool>(verified);
map['deleted'] = Variable<bool>(deleted);
map['account_deleted'] = Variable<bool>(accountDeleted);
map['created_at'] = Variable<DateTime>(createdAt);
return map;
}
@ -284,15 +310,16 @@ class Contact extends DataClass implements Insertable<Contact> {
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<Contact> {
username: serializer.fromJson<String>(json['username']),
displayName: serializer.fromJson<String?>(json['displayName']),
nickName: serializer.fromJson<String?>(json['nickName']),
avatarSvg: serializer.fromJson<String?>(json['avatarSvg']),
avatarSvgCompressed:
serializer.fromJson<Uint8List?>(json['avatarSvgCompressed']),
senderProfileCounter:
serializer.fromJson<int>(json['senderProfileCounter']),
accepted: serializer.fromJson<bool>(json['accepted']),
deletedByUser: serializer.fromJson<bool>(json['deletedByUser']),
requested: serializer.fromJson<bool>(json['requested']),
blocked: serializer.fromJson<bool>(json['blocked']),
verified: serializer.fromJson<bool>(json['verified']),
deleted: serializer.fromJson<bool>(json['deleted']),
accountDeleted: serializer.fromJson<bool>(json['accountDeleted']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
);
}
@ -324,13 +353,14 @@ class Contact extends DataClass implements Insertable<Contact> {
'username': serializer.toJson<String>(username),
'displayName': serializer.toJson<String?>(displayName),
'nickName': serializer.toJson<String?>(nickName),
'avatarSvg': serializer.toJson<String?>(avatarSvg),
'avatarSvgCompressed': serializer.toJson<Uint8List?>(avatarSvgCompressed),
'senderProfileCounter': serializer.toJson<int>(senderProfileCounter),
'accepted': serializer.toJson<bool>(accepted),
'deletedByUser': serializer.toJson<bool>(deletedByUser),
'requested': serializer.toJson<bool>(requested),
'blocked': serializer.toJson<bool>(blocked),
'verified': serializer.toJson<bool>(verified),
'deleted': serializer.toJson<bool>(deleted),
'accountDeleted': serializer.toJson<bool>(accountDeleted),
'createdAt': serializer.toJson<DateTime>(createdAt),
};
}
@ -340,26 +370,30 @@ class Contact extends DataClass implements Insertable<Contact> {
String? username,
Value<String?> displayName = const Value.absent(),
Value<String?> nickName = const Value.absent(),
Value<String?> avatarSvg = const Value.absent(),
Value<Uint8List?> 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<Contact> {
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<Contact> {
..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<Contact> {
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<Contact> {
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<Contact> {
final Value<String> username;
final Value<String?> displayName;
final Value<String?> nickName;
final Value<String?> avatarSvg;
final Value<Uint8List?> avatarSvgCompressed;
final Value<int> senderProfileCounter;
final Value<bool> accepted;
final Value<bool> deletedByUser;
final Value<bool> requested;
final Value<bool> blocked;
final Value<bool> verified;
final Value<bool> deleted;
final Value<bool> accountDeleted;
final Value<DateTime> 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<Contact> {
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<Contact> custom({
@ -479,13 +527,14 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Expression<String>? username,
Expression<String>? displayName,
Expression<String>? nickName,
Expression<String>? avatarSvg,
Expression<Uint8List>? avatarSvgCompressed,
Expression<int>? senderProfileCounter,
Expression<bool>? accepted,
Expression<bool>? deletedByUser,
Expression<bool>? requested,
Expression<bool>? blocked,
Expression<bool>? verified,
Expression<bool>? deleted,
Expression<bool>? accountDeleted,
Expression<DateTime>? createdAt,
}) {
return RawValuesInsertable({
@ -493,14 +542,16 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
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<Contact> {
Value<String>? username,
Value<String?>? displayName,
Value<String?>? nickName,
Value<String?>? avatarSvg,
Value<Uint8List?>? avatarSvgCompressed,
Value<int>? senderProfileCounter,
Value<bool>? accepted,
Value<bool>? deletedByUser,
Value<bool>? requested,
Value<bool>? blocked,
Value<bool>? verified,
Value<bool>? deleted,
Value<bool>? accountDeleted,
Value<DateTime>? 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<Contact> {
if (nickName.present) {
map['nick_name'] = Variable<String>(nickName.value);
}
if (avatarSvg.present) {
map['avatar_svg'] = Variable<String>(avatarSvg.value);
if (avatarSvgCompressed.present) {
map['avatar_svg_compressed'] =
Variable<Uint8List>(avatarSvgCompressed.value);
}
if (senderProfileCounter.present) {
map['sender_profile_counter'] = Variable<int>(senderProfileCounter.value);
@ -558,6 +612,9 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
if (accepted.present) {
map['accepted'] = Variable<bool>(accepted.value);
}
if (deletedByUser.present) {
map['deleted_by_user'] = Variable<bool>(deletedByUser.value);
}
if (requested.present) {
map['requested'] = Variable<bool>(requested.value);
}
@ -567,8 +624,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
if (verified.present) {
map['verified'] = Variable<bool>(verified.value);
}
if (deleted.present) {
map['deleted'] = Variable<bool>(deleted.value);
if (accountDeleted.present) {
map['account_deleted'] = Variable<bool>(accountDeleted.value);
}
if (createdAt.present) {
map['created_at'] = Variable<DateTime>(createdAt.value);
@ -583,13 +640,14 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
..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<String?> displayName,
Value<String?> nickName,
Value<String?> avatarSvg,
Value<Uint8List?> avatarSvgCompressed,
Value<int> senderProfileCounter,
Value<bool> accepted,
Value<bool> deletedByUser,
Value<bool> requested,
Value<bool> blocked,
Value<bool> verified,
Value<bool> deleted,
Value<bool> accountDeleted,
Value<DateTime> createdAt,
});
typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({
@ -6547,13 +6606,14 @@ typedef $$ContactsTableUpdateCompanionBuilder = ContactsCompanion Function({
Value<String> username,
Value<String?> displayName,
Value<String?> nickName,
Value<String?> avatarSvg,
Value<Uint8List?> avatarSvgCompressed,
Value<int> senderProfileCounter,
Value<bool> accepted,
Value<bool> deletedByUser,
Value<bool> requested,
Value<bool> blocked,
Value<bool> verified,
Value<bool> deleted,
Value<bool> accountDeleted,
Value<DateTime> createdAt,
});
@ -6684,8 +6744,9 @@ class $$ContactsTableFilterComposer
ColumnFilters<String> get nickName => $composableBuilder(
column: $table.nickName, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get avatarSvg => $composableBuilder(
column: $table.avatarSvg, builder: (column) => ColumnFilters(column));
ColumnFilters<Uint8List> get avatarSvgCompressed => $composableBuilder(
column: $table.avatarSvgCompressed,
builder: (column) => ColumnFilters(column));
ColumnFilters<int> get senderProfileCounter => $composableBuilder(
column: $table.senderProfileCounter,
@ -6694,6 +6755,9 @@ class $$ContactsTableFilterComposer
ColumnFilters<bool> get accepted => $composableBuilder(
column: $table.accepted, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get deletedByUser => $composableBuilder(
column: $table.deletedByUser, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get requested => $composableBuilder(
column: $table.requested, builder: (column) => ColumnFilters(column));
@ -6703,8 +6767,9 @@ class $$ContactsTableFilterComposer
ColumnFilters<bool> get verified => $composableBuilder(
column: $table.verified, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get deleted => $composableBuilder(
column: $table.deleted, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get accountDeleted => $composableBuilder(
column: $table.accountDeleted,
builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnFilters(column));
@ -6861,8 +6926,9 @@ class $$ContactsTableOrderingComposer
ColumnOrderings<String> get nickName => $composableBuilder(
column: $table.nickName, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get avatarSvg => $composableBuilder(
column: $table.avatarSvg, builder: (column) => ColumnOrderings(column));
ColumnOrderings<Uint8List> get avatarSvgCompressed => $composableBuilder(
column: $table.avatarSvgCompressed,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get senderProfileCounter => $composableBuilder(
column: $table.senderProfileCounter,
@ -6871,6 +6937,10 @@ class $$ContactsTableOrderingComposer
ColumnOrderings<bool> get accepted => $composableBuilder(
column: $table.accepted, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get deletedByUser => $composableBuilder(
column: $table.deletedByUser,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get requested => $composableBuilder(
column: $table.requested, builder: (column) => ColumnOrderings(column));
@ -6880,8 +6950,9 @@ class $$ContactsTableOrderingComposer
ColumnOrderings<bool> get verified => $composableBuilder(
column: $table.verified, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get deleted => $composableBuilder(
column: $table.deleted, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get accountDeleted => $composableBuilder(
column: $table.accountDeleted,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
@ -6908,8 +6979,8 @@ class $$ContactsTableAnnotationComposer
GeneratedColumn<String> get nickName =>
$composableBuilder(column: $table.nickName, builder: (column) => column);
GeneratedColumn<String> get avatarSvg =>
$composableBuilder(column: $table.avatarSvg, builder: (column) => column);
GeneratedColumn<Uint8List> get avatarSvgCompressed => $composableBuilder(
column: $table.avatarSvgCompressed, builder: (column) => column);
GeneratedColumn<int> get senderProfileCounter => $composableBuilder(
column: $table.senderProfileCounter, builder: (column) => column);
@ -6917,6 +6988,9 @@ class $$ContactsTableAnnotationComposer
GeneratedColumn<bool> get accepted =>
$composableBuilder(column: $table.accepted, builder: (column) => column);
GeneratedColumn<bool> get deletedByUser => $composableBuilder(
column: $table.deletedByUser, builder: (column) => column);
GeneratedColumn<bool> get requested =>
$composableBuilder(column: $table.requested, builder: (column) => column);
@ -6926,8 +7000,8 @@ class $$ContactsTableAnnotationComposer
GeneratedColumn<bool> get verified =>
$composableBuilder(column: $table.verified, builder: (column) => column);
GeneratedColumn<bool> get deleted =>
$composableBuilder(column: $table.deleted, builder: (column) => column);
GeneratedColumn<bool> get accountDeleted => $composableBuilder(
column: $table.accountDeleted, builder: (column) => column);
GeneratedColumn<DateTime> get createdAt =>
$composableBuilder(column: $table.createdAt, builder: (column) => column);
@ -7097,13 +7171,14 @@ class $$ContactsTableTableManager extends RootTableManager<
Value<String> username = const Value.absent(),
Value<String?> displayName = const Value.absent(),
Value<String?> nickName = const Value.absent(),
Value<String?> avatarSvg = const Value.absent(),
Value<Uint8List?> avatarSvgCompressed = const Value.absent(),
Value<int> senderProfileCounter = const Value.absent(),
Value<bool> accepted = const Value.absent(),
Value<bool> deletedByUser = const Value.absent(),
Value<bool> requested = const Value.absent(),
Value<bool> blocked = const Value.absent(),
Value<bool> verified = const Value.absent(),
Value<bool> deleted = const Value.absent(),
Value<bool> accountDeleted = const Value.absent(),
Value<DateTime> 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<String?> displayName = const Value.absent(),
Value<String?> nickName = const Value.absent(),
Value<String?> avatarSvg = const Value.absent(),
Value<Uint8List?> avatarSvgCompressed = const Value.absent(),
Value<int> senderProfileCounter = const Value.absent(),
Value<bool> accepted = const Value.absent(),
Value<bool> deletedByUser = const Value.absent(),
Value<bool> requested = const Value.absent(),
Value<bool> blocked = const Value.absent(),
Value<bool> verified = const Value.absent(),
Value<bool> deleted = const Value.absent(),
Value<bool> accountDeleted = const Value.absent(),
Value<DateTime> 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

View file

@ -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",

View file

@ -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",

View file

@ -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:

View file

@ -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';

View file

@ -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';

View file

@ -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<EncryptedContent_ContactUpdate_Type>(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);

View file

@ -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==');

View file

@ -118,7 +118,7 @@ message EncryptedContent {
}
Type type = 1;
optional string avatarSvg = 2;
optional bytes avatarSvgCompressed = 2;
optional string displayName = 3;
}

View file

@ -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})'),
),
);

View file

@ -52,6 +52,8 @@ Future<void> 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(

View file

@ -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<void> 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<void> 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,
),
);

View file

@ -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<void> 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<void> 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<void> 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<PlaintextContent?> 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()) {

View file

@ -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<void> 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<void> 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<void> 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),
),

View file

@ -101,6 +101,8 @@ Future<void> 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,

View file

@ -29,6 +29,10 @@ Future<void> 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}');
}

View file

@ -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<T, E> {
Result.error(this.error) : value = null;
@ -57,7 +56,7 @@ ClientToServer createClientToServerFromApplicationData(
return ClientToServer()..v0 = v0;
}
Future<void> rejectAndDeleteContact(int contactId) async {
Future<void> rejectAndHideContact(int contactId) async {
await sendCipherText(
contactId,
EncryptedContent(
@ -66,9 +65,14 @@ Future<void> 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<void> handleMediaError(MediaFile media) async {

View file

@ -9,7 +9,7 @@ Future<void> 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;
}

View file

@ -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<NotificationResponse> selectNotificationStream =
StreamController<NotificationResponse>.broadcast();
@ -61,10 +62,11 @@ Future<void> 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);

View file

@ -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);
}

View file

@ -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<AddNewUserView> {
}
Future<void> _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<AddNewUserView> {
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(

View file

@ -47,7 +47,7 @@ class _ChatListViewState extends State<ChatListView> {
}
Future<void> initAsync() async {
final stream = twonlyDB.groupsDao.watchGroups();
final stream = twonlyDB.groupsDao.watchGroupsForChatList();
_contactsSub = stream.listen((groups) {
setState(() {
_groupsNotPinned = groups.where((x) => !x.pinned).toList();

View file

@ -38,7 +38,9 @@ class _UserListItem extends State<GroupListItem> {
late StreamSubscription<List<Message>> messagesNotOpenedStream;
Message? lastMessage;
Reaction? lastReaction;
late StreamSubscription<Message?> lastMessageStream;
late StreamSubscription<Reaction?> lastReactionStream;
late StreamSubscription<List<MediaFile>> lastMediaFilesStream;
List<Message> previewMessages = [];
@ -54,6 +56,7 @@ class _UserListItem extends State<GroupListItem> {
@override
void dispose() {
messagesNotOpenedStream.cancel();
lastReactionStream.cancel();
lastMessageStream.cancel();
lastMediaFilesStream.cancel();
super.dispose();
@ -68,6 +71,17 @@ class _UserListItem extends State<GroupListItem> {
});
});
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<GroupListItem> {
lastMessage = newLastMessage;
messagesNotOpened = newMessagesNotOpened;
setState(() {
// sets lastMessages, messagesNotOpened and currentMessage
});
if (mounted) setState(() {});
}
Future<void> onTap() async {
@ -217,7 +229,11 @@ class _UserListItem extends State<GroupListItem> {
? Text(context.lang.chatsTapToSend)
: Row(
children: [
MessageSendStateIcon(previewMessages, previewMediaFiles),
MessageSendStateIcon(
previewMessages,
previewMediaFiles,
lastReaction: lastReaction,
),
const Text(''),
const SizedBox(width: 5),
if (currentMessage != null)

View file

@ -179,7 +179,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
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<ChatListEntry> {
if (nextMessage != null) {
if (message.senderId == nextMessage.senderId) {
bottom = 10;
bottom = 3;
}
}
@ -203,7 +203,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
final combinesWidthNext = combineTextMessageWithNext(message, nextMessage);
if (combinesWidthNext) {
bottom = 1;
bottom = 0;
bottomLeft = 5.0;
}

View file

@ -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<Message> messages;
final List<MediaFile> mediaFiles;
final Reaction? lastReaction;
final MainAxisAlignment mainAxisAlignment;
final bool canBeReopened;
@ -125,8 +128,8 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
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<MessageSendStateIcon> {
}
}
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<MessageSendStateIcon> {
}
}
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];

View file

@ -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<AvatarIcon> {
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(() {});
}

View file

@ -30,7 +30,7 @@ class _ContactViewState extends State<ContactView> {
);
if (remove) {
// trigger deletion for the other user...
await rejectAndDeleteContact(contact.userId);
await rejectAndHideContact(contact.userId);
if (mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
}

View file

@ -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<DatabaseMigrationView> {
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 =