mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 05:22:13 +00:00
notification of the verified user
This commit is contained in:
parent
e1f28e1b87
commit
7d09bd7283
34 changed files with 957 additions and 270 deletions
|
|
@ -12,6 +12,7 @@ import 'package:twonly/core/frb_generated.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/callbacks/callbacks.dart';
|
import 'package:twonly/src/callbacks/callbacks.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
|
|
@ -121,15 +122,25 @@ Future<void> runMigrations() async {
|
||||||
if (userService.currentUser.appVersion < 90) {
|
if (userService.currentUser.appVersion < 90) {
|
||||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||||
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
|
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
|
||||||
await updateUser((u) {
|
await updateUser((u) => u.appVersion = 90);
|
||||||
u.appVersion = 90;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userService.currentUser.appVersion < 91) {
|
if (userService.currentUser.appVersion < 91) {
|
||||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||||
await makeMigrationToVersion91();
|
await makeMigrationToVersion91();
|
||||||
await updateUser((u) {
|
await updateUser((u) => u.appVersion = 91);
|
||||||
u.appVersion = 91;
|
}
|
||||||
});
|
|
||||||
|
if (userService.currentUser.appVersion < 109) {
|
||||||
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
|
for (final contact in contacts) {
|
||||||
|
if (contact.verified) {
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
contact.userId,
|
||||||
|
VerificationType.migratedFromOldVersion,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await updateUser((u) => u.appVersion = 109);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'contacts.dao.g.dart';
|
part 'contacts.dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [Contacts])
|
@DriftAccessor(tables: [Contacts, KeyVerifications])
|
||||||
class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
|
@ -99,6 +99,21 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
)..where((t) => t.userId.equals(userid))).watchSingleOrNull();
|
)..where((t) => t.userId.equals(userid))).watchSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<(Contact, bool)?> watchContactAndVerificationState(int userid) {
|
||||||
|
final query = (select(contacts)..where((t) => t.userId.equals(userid))).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
keyVerifications,
|
||||||
|
keyVerifications.contactId.equalsExp(contacts.userId),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
return query
|
||||||
|
.map((row) => (
|
||||||
|
row.readTable(contacts),
|
||||||
|
row.readTableOrNull(keyVerifications) != null,
|
||||||
|
))
|
||||||
|
.watchSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Contact>> getAllContacts() {
|
Future<List<Contact>> getAllContacts() {
|
||||||
return select(contacts).get();
|
return select(contacts).get();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ part of 'contacts.dao.dart';
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$ContactsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
mixin _$ContactsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
$ContactsTable get contacts => attachedDatabase.contacts;
|
||||||
|
$KeyVerificationsTable get keyVerifications =>
|
||||||
|
attachedDatabase.keyVerifications;
|
||||||
ContactsDaoManager get managers => ContactsDaoManager(this);
|
ContactsDaoManager get managers => ContactsDaoManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,4 +15,9 @@ class ContactsDaoManager {
|
||||||
ContactsDaoManager(this._db);
|
ContactsDaoManager(this._db);
|
||||||
$$ContactsTableTableManager get contacts =>
|
$$ContactsTableTableManager get contacts =>
|
||||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||||
|
$$KeyVerificationsTableTableManager get keyVerifications =>
|
||||||
|
$$KeyVerificationsTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.keyVerifications,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
lib/src/database/daos/key_verification.dao.dart
Normal file
81
lib/src/database/daos/key_verification.dao.dart
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
|
part 'key_verification.dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(
|
||||||
|
tables: [Contacts, VerificationTokens, KeyVerifications, GroupMembers],
|
||||||
|
)
|
||||||
|
class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
||||||
|
with _$KeyVerificationDaoMixin {
|
||||||
|
// ignore: matching_super_parameters
|
||||||
|
KeyVerificationDao(super.db);
|
||||||
|
|
||||||
|
Future<List<VerificationToken>> getRecentVerificationTokens() {
|
||||||
|
final cutoff = DateTime.now().subtract(const Duration(hours: 24));
|
||||||
|
return (select(
|
||||||
|
verificationTokens,
|
||||||
|
)..where((t) => t.createdAt.isBiggerOrEqualValue(cutoff))).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> insertVerificationToken(Uint8List token) {
|
||||||
|
return into(verificationTokens).insert(
|
||||||
|
VerificationTokensCompanion.insert(token: token),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a map of contactId → the verification type of the earliest
|
||||||
|
/// [KeyVerification] row for that contact.
|
||||||
|
Future<Map<int, VerificationType>>
|
||||||
|
getFirstVerificationTypeByContacts() async {
|
||||||
|
final rows = await (select(
|
||||||
|
keyVerifications,
|
||||||
|
)..orderBy([(kv) => OrderingTerm.asc(kv.createdAt)])).get();
|
||||||
|
|
||||||
|
final result = <int, VerificationType>{};
|
||||||
|
for (final row in rows) {
|
||||||
|
result.putIfAbsent(row.contactId, () => row.type);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isContactVerified(int contactId) async {
|
||||||
|
final row =
|
||||||
|
await (select(keyVerifications)
|
||||||
|
..where((kv) => kv.contactId.equals(contactId))
|
||||||
|
..limit(1))
|
||||||
|
.getSingleOrNull();
|
||||||
|
return row != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<KeyVerification>> watchContactVerification(int contactId) {
|
||||||
|
return (select(
|
||||||
|
keyVerifications,
|
||||||
|
)..where((kv) => kv.contactId.equals(contactId))).watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<bool> watchAllGroupMembersVerified(String groupId) {
|
||||||
|
final gm = groupMembers;
|
||||||
|
final kv = keyVerifications;
|
||||||
|
|
||||||
|
final query = (select(gm)..where((m) => m.groupId.equals(groupId))).join([
|
||||||
|
leftOuterJoin(kv, kv.contactId.equalsExp(gm.contactId)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return query.watch().map(
|
||||||
|
(rows) =>
|
||||||
|
rows.isNotEmpty && rows.every((r) => r.readTableOrNull(kv) != null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addKeyVerification(int contactId, VerificationType type) async {
|
||||||
|
await into(keyVerifications).insertOnConflictUpdate(
|
||||||
|
KeyVerificationsCompanion(
|
||||||
|
contactId: Value(contactId),
|
||||||
|
type: Value(type),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
lib/src/database/daos/key_verification.dao.g.dart
Normal file
36
lib/src/database/daos/key_verification.dao.g.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'key_verification.dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$KeyVerificationDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
|
$ContactsTable get contacts => attachedDatabase.contacts;
|
||||||
|
$VerificationTokensTable get verificationTokens =>
|
||||||
|
attachedDatabase.verificationTokens;
|
||||||
|
$KeyVerificationsTable get keyVerifications =>
|
||||||
|
attachedDatabase.keyVerifications;
|
||||||
|
$GroupsTable get groups => attachedDatabase.groups;
|
||||||
|
$GroupMembersTable get groupMembers => attachedDatabase.groupMembers;
|
||||||
|
KeyVerificationDaoManager get managers => KeyVerificationDaoManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyVerificationDaoManager {
|
||||||
|
final _$KeyVerificationDaoMixin _db;
|
||||||
|
KeyVerificationDaoManager(this._db);
|
||||||
|
$$ContactsTableTableManager get contacts =>
|
||||||
|
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||||
|
$$VerificationTokensTableTableManager get verificationTokens =>
|
||||||
|
$$VerificationTokensTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.verificationTokens,
|
||||||
|
);
|
||||||
|
$$KeyVerificationsTableTableManager get keyVerifications =>
|
||||||
|
$$KeyVerificationsTableTableManager(
|
||||||
|
_db.attachedDatabase,
|
||||||
|
_db.keyVerifications,
|
||||||
|
);
|
||||||
|
$$GroupsTableTableManager get groups =>
|
||||||
|
$$GroupsTableTableManager(_db.attachedDatabase, _db.groups);
|
||||||
|
$$GroupMembersTableTableManager get groupMembers =>
|
||||||
|
$$GroupMembersTableTableManager(_db.attachedDatabase, _db.groupMembers);
|
||||||
|
}
|
||||||
|
|
@ -38,8 +38,11 @@ class Contacts extends Table {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VerificationType {
|
enum VerificationType {
|
||||||
qr,
|
migratedFromOldVersion,
|
||||||
|
qrScanned,
|
||||||
link,
|
link,
|
||||||
|
secretQrToken,
|
||||||
|
contactSharedByVerified,
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('KeyVerification')
|
@DataClassName('KeyVerification')
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:drift_flutter/drift_flutter.dart'
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/groups.dao.dart';
|
import 'package:twonly/src/database/daos/groups.dao.dart';
|
||||||
|
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/messages.dao.dart';
|
import 'package:twonly/src/database/daos/messages.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
||||||
|
|
@ -60,6 +61,7 @@ part 'twonly.db.g.dart';
|
||||||
ReactionsDao,
|
ReactionsDao,
|
||||||
MediaFilesDao,
|
MediaFilesDao,
|
||||||
UserDiscoveryDao,
|
UserDiscoveryDao,
|
||||||
|
KeyVerificationDao,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class TwonlyDB extends _$TwonlyDB {
|
class TwonlyDB extends _$TwonlyDB {
|
||||||
|
|
|
||||||
|
|
@ -11374,6 +11374,9 @@ abstract class _$TwonlyDB extends GeneratedDatabase {
|
||||||
late final UserDiscoveryDao userDiscoveryDao = UserDiscoveryDao(
|
late final UserDiscoveryDao userDiscoveryDao = UserDiscoveryDao(
|
||||||
this as TwonlyDB,
|
this as TwonlyDB,
|
||||||
);
|
);
|
||||||
|
late final KeyVerificationDao keyVerificationDao = KeyVerificationDao(
|
||||||
|
this as TwonlyDB,
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
|
|
|
||||||
|
|
@ -970,6 +970,42 @@ abstract class AppLocalizations {
|
||||||
/// **'Clear verification'**
|
/// **'Clear verification'**
|
||||||
String get contactVerifyNumberClearVerification;
|
String get contactVerifyNumberClearVerification;
|
||||||
|
|
||||||
|
/// No description provided for @userVerifiedTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'User verified'**
|
||||||
|
String get userVerifiedTitle;
|
||||||
|
|
||||||
|
/// No description provided for @verificationTypeQrScanned.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You scanned their QR code.'**
|
||||||
|
String get verificationTypeQrScanned;
|
||||||
|
|
||||||
|
/// No description provided for @verificationTypeSecretQrToken.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'The other person scanned your QR code.'**
|
||||||
|
String get verificationTypeSecretQrToken;
|
||||||
|
|
||||||
|
/// No description provided for @verificationTypeLink.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Verified via link.'**
|
||||||
|
String get verificationTypeLink;
|
||||||
|
|
||||||
|
/// No description provided for @verificationTypeContactSharedByVerified.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Contact received from a verified contact.'**
|
||||||
|
String get verificationTypeContactSharedByVerified;
|
||||||
|
|
||||||
|
/// No description provided for @verificationTypeMigratedFromOldVersion.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Migrated from old version.'**
|
||||||
|
String get verificationTypeMigratedFromOldVersion;
|
||||||
|
|
||||||
/// No description provided for @contactVerifyNumberLongDesc.
|
/// No description provided for @contactVerifyNumberLongDesc.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,27 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberClearVerification => 'Verifizierung aufheben';
|
String get contactVerifyNumberClearVerification => 'Verifizierung aufheben';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userVerifiedTitle => 'Benutzer verifiziert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeQrScanned => 'Du hast den QR-Code gescannt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeSecretQrToken =>
|
||||||
|
'Die andere Person hat deinen QR-Code gescannt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeLink => 'Per Link verifiziert.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeContactSharedByVerified =>
|
||||||
|
'Von einem verifizierten Kontakt geteilt bekommen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeMigratedFromOldVersion =>
|
||||||
|
'Von alter Version migriert';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String contactVerifyNumberLongDesc(Object username) {
|
String contactVerifyNumberLongDesc(Object username) {
|
||||||
return 'Um die Ende-zu-Ende-Verschlüsselung mit $username zu verifizieren, vergleiche die Zahlen mit deren Gerät. Die Person kann auch deinen Code mit deren Gerät scannen.';
|
return 'Um die Ende-zu-Ende-Verschlüsselung mit $username zu verifizieren, vergleiche die Zahlen mit deren Gerät. Die Person kann auch deinen Code mit deren Gerät scannen.';
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,27 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberClearVerification => 'Clear verification';
|
String get contactVerifyNumberClearVerification => 'Clear verification';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userVerifiedTitle => 'User verified';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeQrScanned => 'You scanned their QR code.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeSecretQrToken =>
|
||||||
|
'The other person scanned your QR code.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeLink => 'Verified via link.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeContactSharedByVerified =>
|
||||||
|
'Contact received from a verified contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeMigratedFromOldVersion =>
|
||||||
|
'Migrated from old version.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String contactVerifyNumberLongDesc(Object username) {
|
String contactVerifyNumberLongDesc(Object username) {
|
||||||
return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.';
|
return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.';
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,27 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberClearVerification => 'Clear verification';
|
String get contactVerifyNumberClearVerification => 'Clear verification';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get userVerifiedTitle => 'User verified';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeQrScanned => 'You scanned their QR code.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeSecretQrToken =>
|
||||||
|
'The other person scanned your QR code.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeLink => 'Verified via link.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeContactSharedByVerified =>
|
||||||
|
'Contact received from a verified contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get verificationTypeMigratedFromOldVersion =>
|
||||||
|
'Migrated from old version.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String contactVerifyNumberLongDesc(Object username) {
|
String contactVerifyNumberLongDesc(Object username) {
|
||||||
return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.';
|
return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.';
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 57ec512977e514fca6413622bb4a7e03701f09a0
|
Subproject commit a35f6a65cf87db6e1b48dea1c0260b59b52b21f1
|
||||||
|
|
@ -1857,6 +1857,68 @@ class EncryptedContent_UserDiscoveryUpdate extends $pb.GeneratedMessage {
|
||||||
$pb.PbList<$core.List<$core.int>> get messages => $_getList(0);
|
$pb.PbList<$core.List<$core.int>> get messages => $_getList(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EncryptedContent_KeyVerificationProof extends $pb.GeneratedMessage {
|
||||||
|
factory EncryptedContent_KeyVerificationProof({
|
||||||
|
$core.List<$core.int>? calculatedMac,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (calculatedMac != null) result.calculatedMac = calculatedMac;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
EncryptedContent_KeyVerificationProof._();
|
||||||
|
|
||||||
|
factory EncryptedContent_KeyVerificationProof.fromBuffer(
|
||||||
|
$core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory EncryptedContent_KeyVerificationProof.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'EncryptedContent.KeyVerificationProof',
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'calculatedMac', $pb.PbFieldType.OY)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
EncryptedContent_KeyVerificationProof clone() =>
|
||||||
|
EncryptedContent_KeyVerificationProof()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
EncryptedContent_KeyVerificationProof copyWith(
|
||||||
|
void Function(EncryptedContent_KeyVerificationProof) updates) =>
|
||||||
|
super.copyWith((message) =>
|
||||||
|
updates(message as EncryptedContent_KeyVerificationProof))
|
||||||
|
as EncryptedContent_KeyVerificationProof;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static EncryptedContent_KeyVerificationProof create() =>
|
||||||
|
EncryptedContent_KeyVerificationProof._();
|
||||||
|
@$core.override
|
||||||
|
EncryptedContent_KeyVerificationProof createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<EncryptedContent_KeyVerificationProof> createRepeated() =>
|
||||||
|
$pb.PbList<EncryptedContent_KeyVerificationProof>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static EncryptedContent_KeyVerificationProof getDefault() =>
|
||||||
|
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||||
|
EncryptedContent_KeyVerificationProof>(create);
|
||||||
|
static EncryptedContent_KeyVerificationProof? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get calculatedMac => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set calculatedMac($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasCalculatedMac() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearCalculatedMac() => $_clearField(1);
|
||||||
|
}
|
||||||
|
|
||||||
class EncryptedContent extends $pb.GeneratedMessage {
|
class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
factory EncryptedContent({
|
factory EncryptedContent({
|
||||||
$core.String? groupId,
|
$core.String? groupId,
|
||||||
|
|
@ -1881,6 +1943,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
$core.List<$core.int>? senderUserDiscoveryVersion,
|
$core.List<$core.int>? senderUserDiscoveryVersion,
|
||||||
EncryptedContent_UserDiscoveryRequest? userDiscoveryRequest,
|
EncryptedContent_UserDiscoveryRequest? userDiscoveryRequest,
|
||||||
EncryptedContent_UserDiscoveryUpdate? userDiscoveryUpdate,
|
EncryptedContent_UserDiscoveryUpdate? userDiscoveryUpdate,
|
||||||
|
EncryptedContent_KeyVerificationProof? keyVerificationProof,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (groupId != null) result.groupId = groupId;
|
if (groupId != null) result.groupId = groupId;
|
||||||
|
|
@ -1911,6 +1974,8 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
result.userDiscoveryRequest = userDiscoveryRequest;
|
result.userDiscoveryRequest = userDiscoveryRequest;
|
||||||
if (userDiscoveryUpdate != null)
|
if (userDiscoveryUpdate != null)
|
||||||
result.userDiscoveryUpdate = userDiscoveryUpdate;
|
result.userDiscoveryUpdate = userDiscoveryUpdate;
|
||||||
|
if (keyVerificationProof != null)
|
||||||
|
result.keyVerificationProof = keyVerificationProof;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1979,6 +2044,9 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
..aOM<EncryptedContent_UserDiscoveryUpdate>(
|
..aOM<EncryptedContent_UserDiscoveryUpdate>(
|
||||||
23, _omitFieldNames ? '' : 'userDiscoveryUpdate',
|
23, _omitFieldNames ? '' : 'userDiscoveryUpdate',
|
||||||
subBuilder: EncryptedContent_UserDiscoveryUpdate.create)
|
subBuilder: EncryptedContent_UserDiscoveryUpdate.create)
|
||||||
|
..aOM<EncryptedContent_KeyVerificationProof>(
|
||||||
|
24, _omitFieldNames ? '' : 'keyVerificationProof',
|
||||||
|
subBuilder: EncryptedContent_KeyVerificationProof.create)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -2251,6 +2319,19 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
@$pb.TagNumber(23)
|
@$pb.TagNumber(23)
|
||||||
EncryptedContent_UserDiscoveryUpdate ensureUserDiscoveryUpdate() =>
|
EncryptedContent_UserDiscoveryUpdate ensureUserDiscoveryUpdate() =>
|
||||||
$_ensure(21);
|
$_ensure(21);
|
||||||
|
|
||||||
|
@$pb.TagNumber(24)
|
||||||
|
EncryptedContent_KeyVerificationProof get keyVerificationProof => $_getN(22);
|
||||||
|
@$pb.TagNumber(24)
|
||||||
|
set keyVerificationProof(EncryptedContent_KeyVerificationProof value) =>
|
||||||
|
$_setField(24, value);
|
||||||
|
@$pb.TagNumber(24)
|
||||||
|
$core.bool hasKeyVerificationProof() => $_has(22);
|
||||||
|
@$pb.TagNumber(24)
|
||||||
|
void clearKeyVerificationProof() => $_clearField(24);
|
||||||
|
@$pb.TagNumber(24)
|
||||||
|
EncryptedContent_KeyVerificationProof ensureKeyVerificationProof() =>
|
||||||
|
$_ensure(22);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
const $core.bool _omitFieldNames =
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,16 @@ const EncryptedContent$json = {
|
||||||
'10': 'userDiscoveryUpdate',
|
'10': 'userDiscoveryUpdate',
|
||||||
'17': true
|
'17': true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'key_verification_proof',
|
||||||
|
'3': 24,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.EncryptedContent.KeyVerificationProof',
|
||||||
|
'9': 22,
|
||||||
|
'10': 'keyVerificationProof',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'3': [
|
'3': [
|
||||||
EncryptedContent_ErrorMessages$json,
|
EncryptedContent_ErrorMessages$json,
|
||||||
|
|
@ -384,7 +394,8 @@ const EncryptedContent$json = {
|
||||||
EncryptedContent_FlameSync$json,
|
EncryptedContent_FlameSync$json,
|
||||||
EncryptedContent_TypingIndicator$json,
|
EncryptedContent_TypingIndicator$json,
|
||||||
EncryptedContent_UserDiscoveryRequest$json,
|
EncryptedContent_UserDiscoveryRequest$json,
|
||||||
EncryptedContent_UserDiscoveryUpdate$json
|
EncryptedContent_UserDiscoveryUpdate$json,
|
||||||
|
EncryptedContent_KeyVerificationProof$json
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': '_group_id'},
|
{'1': '_group_id'},
|
||||||
|
|
@ -409,6 +420,7 @@ const EncryptedContent$json = {
|
||||||
{'1': '_typing_indicator'},
|
{'1': '_typing_indicator'},
|
||||||
{'1': '_user_discovery_request'},
|
{'1': '_user_discovery_request'},
|
||||||
{'1': '_user_discovery_update'},
|
{'1': '_user_discovery_update'},
|
||||||
|
{'1': '_key_verification_proof'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -911,6 +923,14 @@ const EncryptedContent_UserDiscoveryUpdate$json = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@$core.Deprecated('Use encryptedContentDescriptor instead')
|
||||||
|
const EncryptedContent_KeyVerificationProof$json = {
|
||||||
|
'1': 'KeyVerificationProof',
|
||||||
|
'2': [
|
||||||
|
{'1': 'calculated_mac', '3': 1, '4': 1, '5': 12, '10': 'calculatedMac'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
/// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
||||||
'ChBFbmNyeXB0ZWRDb250ZW50Eh4KCGdyb3VwX2lkGAIgASgJSABSB2dyb3VwSWSIAQESKQoOaX'
|
'ChBFbmNyeXB0ZWRDb250ZW50Eh4KCGdyb3VwX2lkGAIgASgJSABSB2dyb3VwSWSIAQESKQoOaX'
|
||||||
|
|
@ -941,75 +961,79 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
||||||
'gTUg90eXBpbmdJbmRpY2F0b3KIAQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEoCzIm'
|
'gTUg90eXBpbmdJbmRpY2F0b3KIAQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEoCzIm'
|
||||||
'LkVuY3J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVJlcXVlc3RIFFIUdXNlckRpc2NvdmVyeV'
|
'LkVuY3J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVJlcXVlc3RIFFIUdXNlckRpc2NvdmVyeV'
|
||||||
'JlcXVlc3SIAQESXgoVdXNlcl9kaXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVkQ29u'
|
'JlcXVlc3SIAQESXgoVdXNlcl9kaXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVkQ29u'
|
||||||
'dGVudC5Vc2VyRGlzY292ZXJ5VXBkYXRlSBVSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQEa8AEKDU'
|
'dGVudC5Vc2VyRGlzY292ZXJ5VXBkYXRlSBVSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQESYQoWa2'
|
||||||
'Vycm9yTWVzc2FnZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz'
|
'V5X3ZlcmlmaWNhdGlvbl9wcm9vZhgYIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuS2V5VmVyaWZp'
|
||||||
'YWdlcy5UeXBlUgR0eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2'
|
'Y2F0aW9uUHJvb2ZIFlIUa2V5VmVyaWZpY2F0aW9uUHJvb2aIAQEa8AEKDUVycm9yTWVzc2FnZX'
|
||||||
'VpcHRJZCJ3CgRUeXBlEjwKOEVSUk9SX1BST0NFU1NJTkdfTUVTU0FHRV9DUkVBVEVEX0FDQ09V'
|
'MSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeXBlUgR0'
|
||||||
'TlRfUkVRVUVTVF9JTlNURUFEEAASGAoUVU5LTk9XTl9NRVNTQUdFX1RZUEUQAhIXChNTRVNTSU'
|
'eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCJ3CgRUeX'
|
||||||
'9OX09VVF9PRl9TWU5DEAMaVAoLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0'
|
'BlEjwKOEVSUk9SX1BST0NFU1NJTkdfTUVTU0FHRV9DUkVBVEVEX0FDQ09VTlRfUkVRVUVTVF9J'
|
||||||
'ZUtleRIoChBncm91cF9wdWJsaWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRo1CglHcm91cE'
|
'TlNURUFEEAASGAoUVU5LTk9XTl9NRVNTQUdFX1RZUEUQAhIXChNTRVNTSU9OX09VVF9PRl9TWU'
|
||||||
'pvaW4SKAoQZ3JvdXBfcHVibGljX2tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5k'
|
'5DEAMaVAoLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91'
|
||||||
'R3JvdXBQdWJsaWNLZXkayAIKC0dyb3VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEgAS'
|
'cF9wdWJsaWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRo1CglHcm91cEpvaW4SKAoQZ3JvdX'
|
||||||
'gJUg9ncm91cEFjdGlvblR5cGUSMwoTYWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZmZl'
|
'BfcHVibGljX2tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNL'
|
||||||
'Y3RlZENvbnRhY3RJZIgBARIpCg5uZXdfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbW'
|
'ZXkayAIKC0dyb3VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdG'
|
||||||
'WIAQESVwombmV3X2RlbGV0ZV9tZXNzYWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAlIi'
|
'lvblR5cGUSMwoTYWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJ'
|
||||||
'bmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29udG'
|
'ZIgBARIpCg5uZXdfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2'
|
||||||
'FjdF9pZEIRCg9fbmV3X2dyb3VwX25hbWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJf'
|
'RlbGV0ZV9tZXNzYWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVz'
|
||||||
'bWlsbGlzZWNvbmRzGq8BCgtUZXh0TWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCV'
|
'c2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbm'
|
||||||
'IPc2VuZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgD'
|
'V3X2dyb3VwX25hbWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRz'
|
||||||
'Ugl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZI'
|
'Gq8BCgtUZXh0TWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2'
|
||||||
'gBAUITChFfcXVvdGVfbWVzc2FnZV9pZBrOAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEioKEXNl'
|
'FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXAS'
|
||||||
'bmRlcl9tZXNzYWdlX2lkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1wGAIgAS'
|
'LQoQcXVvdGVfbWVzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUITChFfcXVvdG'
|
||||||
'gDUgl0aW1lc3RhbXASEgoEdHlwZRgDIAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3NhZ2Vf'
|
'VfbWVzc2FnZV9pZBrOAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEioKEXNlbmRlcl9tZXNzYWdl'
|
||||||
'ZGF0YRgEIAEoDEgAUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQFCGgoYX2FkZGl0aW9uYWxfbW'
|
'X2lkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1wGAIgASgDUgl0aW1lc3RhbX'
|
||||||
'Vzc2FnZV9kYXRhGmQKCFJlYWN0aW9uEioKEXRhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJn'
|
'ASEgoEdHlwZRgDIAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgEIAEoDEgA'
|
||||||
'ZXRNZXNzYWdlSWQSFAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3'
|
'UhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQFCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGm'
|
||||||
'ZlGr4CCg1NZXNzYWdlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1l'
|
'QKCFJlYWN0aW9uEioKEXRhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQS'
|
||||||
'c3NhZ2VVcGRhdGUuVHlwZVIEdHlwZRIvChFzZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW'
|
'FAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGr4CCg1NZXNzYW'
|
||||||
'5kZXJNZXNzYWdlSWSIAQESPQobbXVsdGlwbGVfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhht'
|
'dlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUu'
|
||||||
'dWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbW'
|
'VHlwZVIEdHlwZRIvChFzZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSW'
|
||||||
'VzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhU'
|
'SIAQESPQobbXVsdGlwbGVfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhhtdWx0aXBsZVRhcmdl'
|
||||||
'EAESCgoGT1BFTkVEEAJCFAoSX3NlbmRlcl9tZXNzYWdlX2lkQgcKBV90ZXh0GoUGCgVNZWRpYR'
|
'dE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1'
|
||||||
'IqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiAB'
|
'IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVE'
|
||||||
'KA4yHC5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhLlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF'
|
'EAJCFAoSX3NlbmRlcl9tZXNzYWdlX2lkQgcKBV90ZXh0GoUGCgVNZWRpYRIqChFzZW5kZXJfbW'
|
||||||
'9pbl9taWxsaXNlY29uZHMYAyABKANIAFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQES'
|
'Vzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0'
|
||||||
'NwoXcmVxdWlyZXNfYXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb2'
|
'ZWRDb250ZW50Lk1lZGlhLlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY2'
|
||||||
'4SHAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEo'
|
'9uZHMYAyABKANIAFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNwoXcmVxdWlyZXNf'
|
||||||
'CUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIqCg5kb3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG'
|
'YXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZXN0YW'
|
||||||
'9hZFRva2VuiAEBEioKDmVuY3J5cHRpb25fa2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQES'
|
'1wGAUgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEoCUgBUg5xdW90ZU1l'
|
||||||
'KgoOZW5jcnlwdGlvbl9tYWMYCSABKAxIBFINZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW'
|
'c3NhZ2VJZIgBARIqCg5kb3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEi'
|
||||||
'9uX25vbmNlGAogASgMSAVSD2VuY3J5cHRpb25Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3Nh'
|
'oKDmVuY3J5cHRpb25fa2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQESKgoOZW5jcnlwdGlv'
|
||||||
'Z2VfZGF0YRgLIAEoDEgGUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQEiPgoEVHlwZRIMCghSRV'
|
'bl9tYWMYCSABKAxIBFINZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW9uX25vbmNlGAogAS'
|
||||||
'VQTE9BRBAAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQiAKHl9k'
|
'gMSAVSD2VuY3J5cHRpb25Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgLIAEo'
|
||||||
'aXNwbGF5X2xpbWl0X2luX21pbGxpc2Vjb25kc0ITChFfcXVvdGVfbWVzc2FnZV9pZEIRCg9fZG'
|
'DEgGUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQEiPgoEVHlwZRIMCghSRVVQTE9BRBAAEgkKBU'
|
||||||
'93bmxvYWRfdG9rZW5CEQoPX2VuY3J5cHRpb25fa2V5QhEKD19lbmNyeXB0aW9uX21hY0ITChFf'
|
'lNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQiAKHl9kaXNwbGF5X2xpbWl0'
|
||||||
'ZW5jcnlwdGlvbl9ub25jZUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEaqQEKC01lZGlhVX'
|
'X2luX21pbGxpc2Vjb25kc0ITChFfcXVvdGVfbWVzc2FnZV9pZEIRCg9fZG93bmxvYWRfdG9rZW'
|
||||||
'BkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVS'
|
'5CEQoPX2VuY3J5cHRpb25fa2V5QhEKD19lbmNyeXB0aW9uX21hY0ITChFfZW5jcnlwdGlvbl9u'
|
||||||
'BHR5cGUSKgoRdGFyZ2V0X21lc3NhZ2VfaWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeX'
|
'b25jZUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEaqQEKC01lZGlhVXBkYXRlEjYKBHR5cG'
|
||||||
'BlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNv'
|
'UYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKgoRdGFy'
|
||||||
'bnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZX'
|
'Z2V0X21lc3NhZ2VfaWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTk'
|
||||||
'F1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUND'
|
'VEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0'
|
||||||
'RVBUEAIapAIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbn'
|
'EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBH'
|
||||||
'QuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjcKFWF2YXRhcl9zdmdfY29tcHJlc3NlZBgCIAEo'
|
'R5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIapAIKDUNv'
|
||||||
'DEgAUhNhdmF0YXJTdmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW'
|
'bnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZG'
|
||||||
'1liAEBEiYKDGRpc3BsYXlfbmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsK'
|
'F0ZS5UeXBlUgR0eXBlEjcKFWF2YXRhcl9zdmdfY29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJT'
|
||||||
'B1JFUVVFU1QQABIKCgZVUERBVEUQAUIYChZfYXZhdGFyX3N2Z19jb21wcmVzc2VkQgsKCV91c2'
|
'dmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiYKDGRpc3'
|
||||||
'VybmFtZUIPCg1fZGlzcGxheV9uYW1lGtkBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5j'
|
'BsYXlfbmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIK'
|
||||||
'cnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhoKBmtleV9pZBgCIAEoA0gAUgVrZX'
|
'CgZVUERBVEUQAUIYChZfYXZhdGFyX3N2Z19jb21wcmVzc2VkQgsKCV91c2VybmFtZUIPCg1fZG'
|
||||||
'lJZIgBARIVCgNrZXkYAyABKAxIAVIDa2V5iAEBEiIKCmNyZWF0ZWRfYXQYBCABKANIAlIJY3Jl'
|
'lzcGxheV9uYW1lGtkBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVu'
|
||||||
'YXRlZEF0iAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQgkKB19rZXlfaWRCBg'
|
'dC5QdXNoS2V5cy5UeXBlUgR0eXBlEhoKBmtleV9pZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZX'
|
||||||
'oEX2tleUINCgtfY3JlYXRlZF9hdBqvAQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASAB'
|
'kYAyABKAxIAVIDa2V5iAEBEiIKCmNyZWF0ZWRfYXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8K'
|
||||||
'KANSDGZsYW1lQ291bnRlchI5ChlsYXN0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYX'
|
'BFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQgkKB19rZXlfaWRCBgoEX2tleUINCgtfY3'
|
||||||
'N0RmxhbWVDb3VudGVyQ2hhbmdlEh8KC2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEK'
|
'JlYXRlZF9hdBqvAQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDGZsYW1lQ291'
|
||||||
'DGZvcmNlX3VwZGF0ZRgEIAEoCFILZm9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCW'
|
'bnRlchI5ChlsYXN0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudG'
|
||||||
'lzX3R5cGluZxgBIAEoCFIIaXNUeXBpbmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0'
|
'VyQ2hhbmdlEh8KC2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcmNlX3VwZGF0'
|
||||||
'Gj8KFFVzZXJEaXNjb3ZlcnlSZXF1ZXN0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3Vycm'
|
'ZRgEIAEoCFILZm9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5cGluZxgBIA'
|
||||||
'VudFZlcnNpb24aMQoTVXNlckRpc2NvdmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVz'
|
'EoCFIIaXNUeXBpbmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFFVzZXJEaXNj'
|
||||||
'c2FnZXNCCwoJX2dyb3VwX2lkQhEKD19pc19kaXJlY3RfY2hhdEIZChdfc2VuZGVyX3Byb2ZpbG'
|
'b3ZlcnlSZXF1ZXN0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZlcnNpb24aMQ'
|
||||||
'VfY291bnRlckIgCh5fc2VuZGVyX3VzZXJfZGlzY292ZXJ5X3ZlcnNpb25CEQoPX21lc3NhZ2Vf'
|
'oTVXNlckRpc2NvdmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZXMaPQoUS2V5'
|
||||||
'dXBkYXRlQggKBl9tZWRpYUIPCg1fbWVkaWFfdXBkYXRlQhEKD19jb250YWN0X3VwZGF0ZUISCh'
|
'VmVyaWZpY2F0aW9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1bGF0ZWRNYW'
|
||||||
'BfY29udGFjdF9yZXF1ZXN0Qg0KC19mbGFtZV9zeW5jQgwKCl9wdXNoX2tleXNCCwoJX3JlYWN0'
|
'NCCwoJX2dyb3VwX2lkQhEKD19pc19kaXJlY3RfY2hhdEIZChdfc2VuZGVyX3Byb2ZpbGVfY291'
|
||||||
'aW9uQg8KDV90ZXh0X21lc3NhZ2VCDwoNX2dyb3VwX2NyZWF0ZUINCgtfZ3JvdXBfam9pbkIPCg'
|
'bnRlckIgCh5fc2VuZGVyX3VzZXJfZGlzY292ZXJ5X3ZlcnNpb25CEQoPX21lc3NhZ2VfdXBkYX'
|
||||||
'1fZ3JvdXBfdXBkYXRlQhoKGF9yZXNlbmRfZ3JvdXBfcHVibGljX2tleUIRCg9fZXJyb3JfbWVz'
|
'RlQggKBl9tZWRpYUIPCg1fbWVkaWFfdXBkYXRlQhEKD19jb250YWN0X3VwZGF0ZUISChBfY29u'
|
||||||
'c2FnZXNCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlQhMKEV90eXBpbmdfaW5kaWNhdG9yQh'
|
'dGFjdF9yZXF1ZXN0Qg0KC19mbGFtZV9zeW5jQgwKCl9wdXNoX2tleXNCCwoJX3JlYWN0aW9uQg'
|
||||||
'kKF191c2VyX2Rpc2NvdmVyeV9yZXF1ZXN0QhgKFl91c2VyX2Rpc2NvdmVyeV91cGRhdGU=');
|
'8KDV90ZXh0X21lc3NhZ2VCDwoNX2dyb3VwX2NyZWF0ZUINCgtfZ3JvdXBfam9pbkIPCg1fZ3Jv'
|
||||||
|
'dXBfdXBkYXRlQhoKGF9yZXNlbmRfZ3JvdXBfcHVibGljX2tleUIRCg9fZXJyb3JfbWVzc2FnZX'
|
||||||
|
'NCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlQhMKEV90eXBpbmdfaW5kaWNhdG9yQhkKF191'
|
||||||
|
'c2VyX2Rpc2NvdmVyeV9yZXF1ZXN0QhgKFl91c2VyX2Rpc2NvdmVyeV91cGRhdGVCGQoXX2tleV'
|
||||||
|
'92ZXJpZmljYXRpb25fcHJvb2Y=');
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ class PublicProfile extends $pb.GeneratedMessage {
|
||||||
$fixnum.Int64? registrationId,
|
$fixnum.Int64? registrationId,
|
||||||
$core.List<$core.int>? signedPrekeySignature,
|
$core.List<$core.int>? signedPrekeySignature,
|
||||||
$fixnum.Int64? signedPrekeyId,
|
$fixnum.Int64? signedPrekeyId,
|
||||||
|
$core.List<$core.int>? secretVerificationToken,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (userId != null) result.userId = userId;
|
if (userId != null) result.userId = userId;
|
||||||
|
|
@ -109,6 +110,8 @@ class PublicProfile extends $pb.GeneratedMessage {
|
||||||
if (signedPrekeySignature != null)
|
if (signedPrekeySignature != null)
|
||||||
result.signedPrekeySignature = signedPrekeySignature;
|
result.signedPrekeySignature = signedPrekeySignature;
|
||||||
if (signedPrekeyId != null) result.signedPrekeyId = signedPrekeyId;
|
if (signedPrekeyId != null) result.signedPrekeyId = signedPrekeyId;
|
||||||
|
if (secretVerificationToken != null)
|
||||||
|
result.secretVerificationToken = secretVerificationToken;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,6 +137,8 @@ class PublicProfile extends $pb.GeneratedMessage {
|
||||||
..a<$core.List<$core.int>>(
|
..a<$core.List<$core.int>>(
|
||||||
6, _omitFieldNames ? '' : 'signedPrekeySignature', $pb.PbFieldType.OY)
|
6, _omitFieldNames ? '' : 'signedPrekeySignature', $pb.PbFieldType.OY)
|
||||||
..aInt64(7, _omitFieldNames ? '' : 'signedPrekeyId')
|
..aInt64(7, _omitFieldNames ? '' : 'signedPrekeyId')
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
8, _omitFieldNames ? '' : 'secretVerificationToken', $pb.PbFieldType.OY)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -220,6 +225,16 @@ class PublicProfile extends $pb.GeneratedMessage {
|
||||||
$core.bool hasSignedPrekeyId() => $_has(6);
|
$core.bool hasSignedPrekeyId() => $_has(6);
|
||||||
@$pb.TagNumber(7)
|
@$pb.TagNumber(7)
|
||||||
void clearSignedPrekeyId() => $_clearField(7);
|
void clearSignedPrekeyId() => $_clearField(7);
|
||||||
|
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
$core.List<$core.int> get secretVerificationToken => $_getN(7);
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
set secretVerificationToken($core.List<$core.int> value) =>
|
||||||
|
$_setBytes(7, value);
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
$core.bool hasSecretVerificationToken() => $_has(7);
|
||||||
|
@$pb.TagNumber(8)
|
||||||
|
void clearSecretVerificationToken() => $_clearField(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
const $core.bool _omitFieldNames =
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,18 @@ const PublicProfile$json = {
|
||||||
'10': 'signedPrekeySignature'
|
'10': 'signedPrekeySignature'
|
||||||
},
|
},
|
||||||
{'1': 'signed_prekey_id', '3': 7, '4': 1, '5': 3, '10': 'signedPrekeyId'},
|
{'1': 'signed_prekey_id', '3': 7, '4': 1, '5': 3, '10': 'signedPrekeyId'},
|
||||||
|
{
|
||||||
|
'1': 'secret_verification_token',
|
||||||
|
'3': 8,
|
||||||
|
'4': 1,
|
||||||
|
'5': 12,
|
||||||
|
'9': 0,
|
||||||
|
'10': 'secretVerificationToken',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
{'1': '_secret_verification_token'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -77,4 +89,5 @@ final $typed_data.Uint8List publicProfileDescriptor = $convert.base64Decode(
|
||||||
'dHlLZXkSIwoNc2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EicKD3JlZ2lzdHJhdG'
|
'dHlLZXkSIwoNc2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EicKD3JlZ2lzdHJhdG'
|
||||||
'lvbl9pZBgFIAEoA1IOcmVnaXN0cmF0aW9uSWQSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUY'
|
'lvbl9pZBgFIAEoA1IOcmVnaXN0cmF0aW9uSWQSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUY'
|
||||||
'BiABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lkGAcgASgDUg'
|
'BiABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lkGAcgASgDUg'
|
||||||
'5zaWduZWRQcmVrZXlJZA==');
|
'5zaWduZWRQcmVrZXlJZBI/ChlzZWNyZXRfdmVyaWZpY2F0aW9uX3Rva2VuGAggASgMSABSF3Nl'
|
||||||
|
'Y3JldFZlcmlmaWNhdGlvblRva2VuiAEBQhwKGl9zZWNyZXRfdmVyaWZpY2F0aW9uX3Rva2Vu');
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ message EncryptedContent {
|
||||||
optional TypingIndicator typing_indicator = 20;
|
optional TypingIndicator typing_indicator = 20;
|
||||||
optional UserDiscoveryRequest user_discovery_request = 22;
|
optional UserDiscoveryRequest user_discovery_request = 22;
|
||||||
optional UserDiscoveryUpdate user_discovery_update = 23;
|
optional UserDiscoveryUpdate user_discovery_update = 23;
|
||||||
|
optional KeyVerificationProof key_verification_proof = 24;
|
||||||
|
|
||||||
message ErrorMessages {
|
message ErrorMessages {
|
||||||
enum Type {
|
enum Type {
|
||||||
|
|
@ -209,4 +210,8 @@ message EncryptedContent {
|
||||||
repeated bytes messages = 1;
|
repeated bytes messages = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message KeyVerificationProof {
|
||||||
|
bytes calculated_mac = 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -16,4 +16,5 @@ message PublicProfile {
|
||||||
int64 registration_id = 5;
|
int64 registration_id = 5;
|
||||||
bytes signed_prekey_signature = 6;
|
bytes signed_prekey_signature = 6;
|
||||||
int64 signed_prekey_id = 7;
|
int64 signed_prekey_id = 7;
|
||||||
|
optional bytes secret_verification_token = 8;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import 'package:twonly/src/services/api/client2client/text_message.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart';
|
import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/messages.api.dart';
|
import 'package:twonly/src/services/api/messages.api.dart';
|
||||||
import 'package:twonly/src/services/group.services.dart';
|
import 'package:twonly/src/services/group.services.dart';
|
||||||
|
import 'package:twonly/src/services/key_verification.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
|
|
@ -330,6 +331,14 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (content.hasKeyVerificationProof()) {
|
||||||
|
await KeyVerificationService.handleVerificationProof(
|
||||||
|
fromUserId,
|
||||||
|
content.keyVerificationProof.calculatedMac,
|
||||||
|
);
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
if (content.hasMediaUpdate()) {
|
if (content.hasMediaUpdate()) {
|
||||||
await handleMediaUpdate(
|
await handleMediaUpdate(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,14 @@ import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -70,22 +69,19 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (storedPublicKey.equals(receivedPublicKey)) {
|
if (storedPublicKey.equals(receivedPublicKey)) {
|
||||||
if (!contact.verified) {
|
final markAsVerified = await showAlertDialog(
|
||||||
final markAsVerified = await showAlertDialog(
|
context,
|
||||||
context,
|
context.lang.linkFromUsername(contact.username),
|
||||||
context.lang.linkFromUsername(contact.username),
|
context.lang.linkFromUsernameLong,
|
||||||
context.lang.linkFromUsernameLong,
|
customOk: context.lang.gotLinkFromFriend,
|
||||||
customOk: context.lang.gotLinkFromFriend,
|
);
|
||||||
|
if (markAsVerified) {
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
contact.userId,
|
||||||
|
VerificationType.link,
|
||||||
);
|
);
|
||||||
if (markAsVerified) {
|
}
|
||||||
await twonlyDB.contactsDao.updateContact(
|
if (context.mounted) {
|
||||||
contact.userId,
|
|
||||||
const ContactsCompanion(
|
|
||||||
verified: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await context.push(Routes.profileContact(contact.userId));
|
await context.push(Routes.profileContact(contact.userId));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
120
lib/src/services/key_verification.service.dart
Normal file
120
lib/src/services/key_verification.service.dart
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||||
|
as pb;
|
||||||
|
import 'package:twonly/src/services/api/messages.api.dart';
|
||||||
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
class KeyVerificationService {
|
||||||
|
static Future<List<int>> getNewSecretVerificationToken() async {
|
||||||
|
final token = getRandomUint8List(16);
|
||||||
|
await twonlyDB.keyVerificationDao.insertVerificationToken(token);
|
||||||
|
return token.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> handleScannedVerificationToken(
|
||||||
|
int contactId,
|
||||||
|
Uint8List contactPubKey,
|
||||||
|
List<int> secretToken,
|
||||||
|
) async {
|
||||||
|
Log.info('Notifying verified user using the secret token');
|
||||||
|
|
||||||
|
final calculatedMac = await _createVerificationBytes(
|
||||||
|
contactId,
|
||||||
|
contactPubKey,
|
||||||
|
secretToken,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await sendCipherText(
|
||||||
|
contactId,
|
||||||
|
pb.EncryptedContent(
|
||||||
|
keyVerificationProof: pb.EncryptedContent_KeyVerificationProof(
|
||||||
|
calculatedMac: calculatedMac,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> handleVerificationProof(
|
||||||
|
int fromUserId,
|
||||||
|
List<int> receivedMac,
|
||||||
|
) async {
|
||||||
|
Log.info('Received a verification proof. Verifying the calculated mac...');
|
||||||
|
|
||||||
|
final contactPubKey = await getPublicKeyFromContact(fromUserId);
|
||||||
|
if (contactPubKey == null) {
|
||||||
|
Log.error('No public key stored..');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final secretTokens = await twonlyDB.keyVerificationDao
|
||||||
|
.getRecentVerificationTokens();
|
||||||
|
for (final secretToken in secretTokens) {
|
||||||
|
final recalculatedMac = await _createVerificationBytes(
|
||||||
|
fromUserId,
|
||||||
|
contactPubKey,
|
||||||
|
secretToken.token,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (recalculatedMac.equals(receivedMac)) {
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
fromUserId,
|
||||||
|
VerificationType.secretQrToken,
|
||||||
|
);
|
||||||
|
Log.info('Contact was verified via secretQrToken');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.error('No valid secret token could be found...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<int>> _createVerificationBytes(
|
||||||
|
int contactId,
|
||||||
|
Uint8List contactPubKey,
|
||||||
|
List<int> secretToken,
|
||||||
|
bool verifying,
|
||||||
|
) async {
|
||||||
|
final bytes = <int>[];
|
||||||
|
|
||||||
|
final userPublicKey = await getUserPublicKey();
|
||||||
|
|
||||||
|
final ownBytes = [
|
||||||
|
..._userIdToLeBytes(userService.currentUser.userId),
|
||||||
|
...userPublicKey,
|
||||||
|
];
|
||||||
|
|
||||||
|
final contactBytes = [..._userIdToLeBytes(contactId), ...contactPubKey];
|
||||||
|
|
||||||
|
if (verifying) {
|
||||||
|
bytes
|
||||||
|
..addAll(ownBytes)
|
||||||
|
..addAll(contactBytes);
|
||||||
|
} else {
|
||||||
|
bytes
|
||||||
|
..addAll(contactBytes)
|
||||||
|
..addAll(ownBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
final hmac = Hmac.sha256();
|
||||||
|
final mac = await hmac.calculateMac(
|
||||||
|
Uint8List.fromList(bytes),
|
||||||
|
secretKey: SecretKey(secretToken),
|
||||||
|
);
|
||||||
|
|
||||||
|
return mac.bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> _userIdToLeBytes(int userId) {
|
||||||
|
final byteData = ByteData(8)..setInt64(0, userId, Endian.little);
|
||||||
|
return byteData.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
@ -91,6 +92,12 @@ Future<SignalIdentity?> getSignalIdentity() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Uint8List> getUserPublicKey() async {
|
||||||
|
final signalIdentity = (await getSignalIdentity())!;
|
||||||
|
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
||||||
|
return (await signalStore.getIdentityKeyPair()).getPublicKey().serialize();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> createIfNotExistsSignalIdentity() async {
|
Future<void> createIfNotExistsSignalIdentity() async {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
@ -7,9 +6,9 @@ import 'package:twonly/core/bridge/wrapper/user_discovery.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/user_discovery/types.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/user_discovery/types.pb.dart';
|
||||||
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/qr.dart';
|
|
||||||
|
|
||||||
class UserDiscoveryService {
|
class UserDiscoveryService {
|
||||||
static Future<void> checkForNewAnnouncedUsers() async {
|
static Future<void> checkForNewAnnouncedUsers() async {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/utils/keyvalue.dart';
|
import 'package:twonly/src/utils/keyvalue.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -43,11 +44,28 @@ Future<void> handleUserStudyUpload() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
|
final verifications = await twonlyDB.keyVerificationDao
|
||||||
|
.getFirstVerificationTypeByContacts();
|
||||||
|
|
||||||
final dataCollection = {
|
final dataCollection = {
|
||||||
'total_contacts': contacts.length,
|
'total_contacts': contacts.length,
|
||||||
'accepted_contacts': contacts.where((c) => c.accepted).length,
|
'accepted_contacts': contacts.where((c) => c.accepted).length,
|
||||||
'verified_contacts': contacts.where((c) => c.verified).length,
|
'verified_contacts': verifications.length,
|
||||||
|
'verified_contacts_via_migrated_from_old_version': verifications.values
|
||||||
|
.where((c) => c == VerificationType.migratedFromOldVersion)
|
||||||
|
.length,
|
||||||
|
'verified_contacts_via_qr_scanned': verifications.values
|
||||||
|
.where((c) => c == VerificationType.qrScanned)
|
||||||
|
.length,
|
||||||
|
'verified_contacts_via_link': verifications.values
|
||||||
|
.where((c) => c == VerificationType.link)
|
||||||
|
.length,
|
||||||
|
'verified_contacts_via_secret_qr_token': verifications.values
|
||||||
|
.where((c) => c == VerificationType.secretQrToken)
|
||||||
|
.length,
|
||||||
|
'verified_contacts_via_contact_shared_by_verified': verifications.values
|
||||||
|
.where((c) => c == VerificationType.contactSharedByVerified)
|
||||||
|
.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,114 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart' show ListExtensions;
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||||
import 'package:twonly/src/services/api/utils.api.dart';
|
import 'package:twonly/src/services/api/utils.api.dart';
|
||||||
|
import 'package:twonly/src/services/key_verification.service.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<Uint8List> getProfileQrCodeData() async {
|
class QrCodeUtils {
|
||||||
final signalIdentity = (await getSignalIdentity())!;
|
static String linkPrefix = 'https://me.twonly.eu/qr/#';
|
||||||
|
|
||||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
static Future<String> publicProfileLink() async {
|
||||||
|
final signalIdentity = (await getSignalIdentity())!;
|
||||||
|
|
||||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
||||||
|
|
||||||
final publicProfile = PublicProfile(
|
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
||||||
userId: Int64(userService.currentUser.userId),
|
|
||||||
username: userService.currentUser.username,
|
|
||||||
publicIdentityKey: (await signalStore.getIdentityKeyPair())
|
|
||||||
.getPublicKey()
|
|
||||||
.serialize(),
|
|
||||||
registrationId: Int64(signalIdentity.registrationId),
|
|
||||||
signedPrekey: signedPreKey.getKeyPair().publicKey.serialize(),
|
|
||||||
signedPrekeySignature: signedPreKey.signature,
|
|
||||||
signedPrekeyId: Int64(signedPreKey.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
final data = publicProfile.writeToBuffer();
|
final secretVerificationToken =
|
||||||
|
await KeyVerificationService.getNewSecretVerificationToken();
|
||||||
|
|
||||||
final qrEnvelope = QREnvelope(
|
final publicProfile = PublicProfile(
|
||||||
type: QREnvelope_Type.PUBLIC_PROFILE,
|
userId: Int64(userService.currentUser.userId),
|
||||||
data: data,
|
username: userService.currentUser.username,
|
||||||
);
|
publicIdentityKey: (await signalStore.getIdentityKeyPair())
|
||||||
|
.getPublicKey()
|
||||||
|
.serialize(),
|
||||||
|
registrationId: Int64(signalIdentity.registrationId),
|
||||||
|
signedPrekey: signedPreKey.getKeyPair().publicKey.serialize(),
|
||||||
|
signedPrekeySignature: signedPreKey.signature,
|
||||||
|
signedPrekeyId: Int64(signedPreKey.id),
|
||||||
|
secretVerificationToken: secretVerificationToken,
|
||||||
|
);
|
||||||
|
|
||||||
return qrEnvelope.writeToBuffer();
|
final data = publicProfile.writeToBuffer();
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List> getUserPublicKey() async {
|
final qrEnvelope = QREnvelope(
|
||||||
final signalIdentity = (await getSignalIdentity())!;
|
type: QREnvelope_Type.PUBLIC_PROFILE,
|
||||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
data: data,
|
||||||
return (await signalStore.getIdentityKeyPair()).getPublicKey().serialize();
|
);
|
||||||
}
|
|
||||||
|
|
||||||
PublicProfile? parseQrCodeData(Uint8List rawBytes) {
|
final bytes = qrEnvelope.writeToBuffer();
|
||||||
try {
|
final urlSafeBase64 = base64Url.encode(bytes);
|
||||||
final envelop = QREnvelope.fromBuffer(rawBytes);
|
|
||||||
if (envelop.type == QREnvelope_Type.PUBLIC_PROFILE) {
|
return '$linkPrefix$urlSafeBase64';
|
||||||
return PublicProfile.fromBuffer(envelop.data);
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
// returns: profile, NEW_USER=true/VERIFIED_USER=false, VERIFICATION_OK
|
||||||
// Log.warn(e);
|
static Future<(PublicProfile, Contact?, bool)?> handleQrCodeLink(
|
||||||
|
String link,
|
||||||
|
) async {
|
||||||
|
late PublicProfile profile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final bytes = base64Url.decode(link.replaceFirst(linkPrefix, ''));
|
||||||
|
final envelope = QREnvelope.fromBuffer(bytes);
|
||||||
|
if (envelope.type != QREnvelope_Type.PUBLIC_PROFILE) return null;
|
||||||
|
profile = PublicProfile.fromBuffer(envelope.data);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final contact = await twonlyDB.contactsDao.getContactById(
|
||||||
|
profile.userId.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (contact == null || !contact.accepted) {
|
||||||
|
if (profile.username == userService.currentUser.username) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// NEW_USER
|
||||||
|
return (profile, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final storedPublicKey = await getPublicKeyFromContact(contact.userId);
|
||||||
|
if (storedPublicKey == null) return null;
|
||||||
|
|
||||||
|
final verificationOk = profile.publicIdentityKey.equals(
|
||||||
|
storedPublicKey.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (verificationOk) {
|
||||||
|
if (profile.hasSecretVerificationToken()) {
|
||||||
|
unawaited(
|
||||||
|
KeyVerificationService.handleScannedVerificationToken(
|
||||||
|
contact.userId,
|
||||||
|
storedPublicKey,
|
||||||
|
profile.secretVerificationToken,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
contact.userId,
|
||||||
|
VerificationType.qrScanned,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (profile, contact, verificationOk);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
||||||
|
|
@ -72,14 +127,28 @@ Future<bool> addNewContactFromPublicProfile(PublicProfile profile) async {
|
||||||
requested: const Value(false),
|
requested: const Value(false),
|
||||||
blocked: const Value(false),
|
blocked: const Value(false),
|
||||||
deletedByUser: const Value(false),
|
deletedByUser: const Value(false),
|
||||||
verified: const Value(
|
|
||||||
true,
|
|
||||||
), // This contact was added from a QR-Code scan, so the public key was not loaded from the server
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The user was added via the profile scanned from the QR code so the scanned public key was used.
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
profile.userId.toInt(),
|
||||||
|
VerificationType.qrScanned,
|
||||||
|
);
|
||||||
|
|
||||||
if (added > 0) {
|
if (added > 0) {
|
||||||
return importSignalContactAndCreateRequest(userdata);
|
if (await importSignalContactAndCreateRequest(userdata)) {
|
||||||
|
if (profile.hasSecretVerificationToken()) {
|
||||||
|
await KeyVerificationService.handleScannedVerificationToken(
|
||||||
|
profile.userId.toInt(),
|
||||||
|
Uint8List.fromList(profile.publicIdentityKey),
|
||||||
|
profile.secretVerificationToken,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,27 +28,29 @@ class VerificationBadgeComp extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
||||||
bool isVerified = false;
|
bool _isVerified = false;
|
||||||
Contact? contact;
|
|
||||||
|
|
||||||
StreamSubscription<List<Contact>>? stream;
|
StreamSubscription<bool>? _streamAllVerified;
|
||||||
|
StreamSubscription<List<KeyVerification>>? _streamContactVerification;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (widget.group != null) {
|
if (widget.group != null) {
|
||||||
stream = twonlyDB.groupsDao
|
_streamAllVerified = twonlyDB.keyVerificationDao
|
||||||
.watchGroupContact(widget.group!.groupId)
|
.watchAllGroupMembersVerified(widget.group!.groupId)
|
||||||
.listen((contacts) {
|
.listen((update) {
|
||||||
if (contacts.length == 1) {
|
|
||||||
contact = contacts.first;
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
isVerified = contacts.every((t) => t.verified);
|
_isVerified = update;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (widget.contact != null) {
|
} else if (widget.contact != null) {
|
||||||
isVerified = widget.contact!.verified;
|
_streamContactVerification = twonlyDB.keyVerificationDao
|
||||||
contact = widget.contact;
|
.watchContactVerification(widget.contact!.userId)
|
||||||
|
.listen((update) {
|
||||||
|
setState(() {
|
||||||
|
_isVerified = update.isNotEmpty;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -56,15 +58,16 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
stream?.cancel();
|
_streamAllVerified?.cancel();
|
||||||
|
_streamContactVerification?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!isVerified && widget.showOnlyIfVerified) return Container();
|
if (!_isVerified && widget.showOnlyIfVerified) return Container();
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: (contact == null || !widget.clickable)
|
onTap: (!widget.clickable)
|
||||||
? null
|
? null
|
||||||
: () => context.push(Routes.settingsHelpFaqVerifyBadge),
|
: () => context.push(Routes.settingsHelpFaqVerifyBadge),
|
||||||
child: ColoredBox(
|
child: ColoredBox(
|
||||||
|
|
@ -77,7 +80,7 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
||||||
bottom: 3,
|
bottom: 3,
|
||||||
),
|
),
|
||||||
child: SvgIcon(
|
child: SvgIcon(
|
||||||
assetPath: isVerified
|
assetPath: _isVerified
|
||||||
? SvgIcons.verifiedGreen
|
? SvgIcons.verifiedGreen
|
||||||
: SvgIcons.verifiedRed,
|
: SvgIcons.verifiedRed,
|
||||||
size: widget.size,
|
size: widget.size,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' show Value;
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -16,7 +14,6 @@ import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.dart';
|
import 'package:twonly/src/utils/qr.dart';
|
||||||
|
|
@ -48,6 +45,7 @@ class MainCameraController {
|
||||||
bool initCameraStarted = true;
|
bool initCameraStarted = true;
|
||||||
Map<int, ScannedVerifiedContact> contactsVerified = {};
|
Map<int, ScannedVerifiedContact> contactsVerified = {};
|
||||||
Map<int, ScannedNewProfile> scannedNewProfiles = {};
|
Map<int, ScannedNewProfile> scannedNewProfiles = {};
|
||||||
|
Set<String> _handledProfileLinks = {};
|
||||||
String? scannedUrl;
|
String? scannedUrl;
|
||||||
GlobalKey zoomButtonKey = GlobalKey();
|
GlobalKey zoomButtonKey = GlobalKey();
|
||||||
GlobalKey cameraPreviewKey = GlobalKey();
|
GlobalKey cameraPreviewKey = GlobalKey();
|
||||||
|
|
@ -340,70 +338,56 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final barcode in barcodes) {
|
for (final barcode in barcodes) {
|
||||||
if (barcode.displayValue != null) {
|
if (barcode.displayValue == null) continue;
|
||||||
if (barcode.displayValue!.startsWith('http://') ||
|
final link = barcode.displayValue!;
|
||||||
barcode.displayValue!.startsWith('https://')) {
|
|
||||||
scannedUrl = barcode.displayValue;
|
|
||||||
if (sharedLinkForPreview == null) {
|
|
||||||
timeSharedLinkWasSetWithQr = clock.now();
|
|
||||||
setSharedLinkForPreview(Uri.parse(scannedUrl!));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (barcode.rawBytes == null) continue;
|
|
||||||
|
|
||||||
final profile = parseQrCodeData(barcode.rawBytes!);
|
if (link.startsWith(QrCodeUtils.linkPrefix)) {
|
||||||
|
if (_handledProfileLinks.contains(link)) continue;
|
||||||
|
_handledProfileLinks.add(link);
|
||||||
|
|
||||||
if (profile == null) continue;
|
final res = await QrCodeUtils.handleQrCodeLink(link);
|
||||||
|
if (res == null) continue;
|
||||||
|
final (profile, contact, verificationOk) = res;
|
||||||
|
|
||||||
final contact = await twonlyDB.contactsDao.getContactById(
|
if (contact == null) {
|
||||||
profile.userId.toInt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contact != null && contact.accepted) {
|
|
||||||
if (contactsVerified[contact.userId] == null) {
|
|
||||||
final storedPublicKey = await getPublicKeyFromContact(
|
|
||||||
contact.userId,
|
|
||||||
);
|
|
||||||
if (storedPublicKey != null) {
|
|
||||||
final verificationOk = profile.publicIdentityKey.equals(
|
|
||||||
storedPublicKey.toList(),
|
|
||||||
);
|
|
||||||
contactsVerified[contact.userId] = ScannedVerifiedContact(
|
|
||||||
contact: contact,
|
|
||||||
verificationOk: verificationOk,
|
|
||||||
);
|
|
||||||
if (verificationOk) {
|
|
||||||
await twonlyDB.contactsDao.updateContact(
|
|
||||||
contact.userId,
|
|
||||||
const ContactsCompanion(verified: Value(true)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await HapticFeedback.heavyImpact();
|
|
||||||
if (verificationOk) {
|
|
||||||
AppGlobalKeys.scaffoldMessengerKey.currentState?.showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
AppGlobalKeys.scaffoldMessengerKey.currentContext?.lang
|
|
||||||
.verifiedPublicKey(
|
|
||||||
getContactDisplayName(contact),
|
|
||||||
) ??
|
|
||||||
'',
|
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 6),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (profile.username != userService.currentUser.username) {
|
|
||||||
if (scannedNewProfiles[profile.userId.toInt()] == null) {
|
if (scannedNewProfiles[profile.userId.toInt()] == null) {
|
||||||
await HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile(
|
scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile(
|
||||||
profile: profile,
|
profile: profile,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contactsVerified[contact.userId] == null) {
|
||||||
|
contactsVerified[contact.userId] = ScannedVerifiedContact(
|
||||||
|
contact: contact,
|
||||||
|
verificationOk: verificationOk,
|
||||||
|
);
|
||||||
|
|
||||||
|
await HapticFeedback.heavyImpact();
|
||||||
|
if (verificationOk) {
|
||||||
|
AppGlobalKeys.scaffoldMessengerKey.currentState?.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
AppGlobalKeys.scaffoldMessengerKey.currentContext?.lang
|
||||||
|
.verifiedPublicKey(
|
||||||
|
getContactDisplayName(contact),
|
||||||
|
) ??
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
duration: const Duration(seconds: 6),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link.startsWith('http://') || link.startsWith('https://')) {
|
||||||
|
scannedUrl = link;
|
||||||
|
if (sharedLinkForPreview == null) {
|
||||||
|
timeSharedLinkWasSetWithQr = clock.now();
|
||||||
|
setSharedLinkForPreview(Uri.parse(scannedUrl!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/utils.api.dart';
|
import 'package:twonly/src/services/api/utils.api.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -130,13 +131,26 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
requested: const Value(false),
|
requested: const Value(false),
|
||||||
blocked: const Value(false),
|
blocked: const Value(false),
|
||||||
deletedByUser: const Value(false),
|
deletedByUser: const Value(false),
|
||||||
verified: Value(
|
|
||||||
!(widget.publicKey == null) &&
|
|
||||||
userdata.publicIdentityKey.equals(widget.publicKey!),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (widget.publicKey != null &&
|
||||||
|
mounted &&
|
||||||
|
widget.publicKey!.equals(userdata.publicIdentityKey)) {
|
||||||
|
final markAsVerified = await showAlertDialog(
|
||||||
|
context,
|
||||||
|
context.lang.linkFromUsername(username),
|
||||||
|
context.lang.linkFromUsernameLong,
|
||||||
|
customOk: context.lang.gotLinkFromFriend,
|
||||||
|
);
|
||||||
|
if (markAsVerified) {
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
userdata.userId.toInt(),
|
||||||
|
VerificationType.link,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (added > 0) await importSignalContactAndCreateRequest(userdata);
|
if (added > 0) await importSignalContactAndCreateRequest(userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
|
||||||
import 'package:twonly/src/services/api/utils.api.dart';
|
import 'package:twonly/src/services/api/utils.api.dart';
|
||||||
|
|
@ -120,13 +122,18 @@ class _ContactRowState extends State<_ContactRow> {
|
||||||
);
|
);
|
||||||
if (userdata == null) return;
|
if (userdata == null) return;
|
||||||
|
|
||||||
var verified = false;
|
if (userdata.publicIdentityKey.equals(widget.contact.publicIdentityKey)) {
|
||||||
if (userdata.publicIdentityKey == widget.contact.publicIdentityKey) {
|
final verified = await twonlyDB.keyVerificationDao.isContactVerified(
|
||||||
final sender = await twonlyDB.contactsDao.getContactById(
|
|
||||||
widget.message.senderId!,
|
widget.message.senderId!,
|
||||||
);
|
);
|
||||||
// in case the sender is verified and the public keys are the same, this trust can be transferred
|
|
||||||
verified = sender != null && sender.verified;
|
if (verified) {
|
||||||
|
Log.info('Verified a user which was shared by a verified contact');
|
||||||
|
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||||
|
userdata.userId.toInt(),
|
||||||
|
VerificationType.contactSharedByVerified,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final added = await twonlyDB.contactsDao.insertOnConflictUpdate(
|
final added = await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||||
|
|
@ -136,9 +143,6 @@ class _ContactRowState extends State<_ContactRow> {
|
||||||
requested: const Value(false),
|
requested: const Value(false),
|
||||||
blocked: const Value(false),
|
blocked: const Value(false),
|
||||||
deletedByUser: const Value(false),
|
deletedByUser: const Value(false),
|
||||||
verified: Value(
|
|
||||||
verified,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:no_screenshot/no_screenshot.dart';
|
import 'package:no_screenshot/no_screenshot.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
|
|
@ -103,43 +104,47 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Mutex _messageUpdateLock = Mutex();
|
||||||
|
|
||||||
Future<void> asyncLoadNextMedia(bool firstRun) async {
|
Future<void> asyncLoadNextMedia(bool firstRun) async {
|
||||||
final messages = twonlyDB.messagesDao.watchMediaNotOpened(
|
final messages = twonlyDB.messagesDao.watchMediaNotOpened(
|
||||||
widget.group.groupId,
|
widget.group.groupId,
|
||||||
);
|
);
|
||||||
|
|
||||||
_subscription = messages.listen((messages) async {
|
_subscription = messages.listen((messages) async {
|
||||||
for (final msg in messages) {
|
await _messageUpdateLock.protect(() async {
|
||||||
if (_alreadyOpenedMediaIds.contains(msg.mediaId)) {
|
for (final msg in messages) {
|
||||||
continue;
|
if (_alreadyOpenedMediaIds.contains(msg.mediaId)) {
|
||||||
}
|
continue;
|
||||||
if (msg.mediaId == null) {
|
}
|
||||||
continue;
|
if (msg.mediaId == null) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.mediaId == currentMedia?.mediaFile.mediaId) {
|
if (msg.mediaId == currentMedia?.mediaFile.mediaId) {
|
||||||
// The update of the current Media in case of a download is done in loadCurrentMediaFile
|
// The update of the current Media in case of a download is done in loadCurrentMediaFile
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the messages was already there just replace it and go to the next...
|
||||||
|
|
||||||
|
final index = allMediaFiles.indexWhere(
|
||||||
|
(m) => m.messageId == msg.messageId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index >= 1) {
|
||||||
|
allMediaFiles[index] = msg;
|
||||||
|
} else if (index == -1) {
|
||||||
|
// If the message does not exist, add it
|
||||||
|
allMediaFiles.add(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
setState(() {});
|
||||||
/// If the messages was already there just replace it and go to the next...
|
if (firstRun) {
|
||||||
|
firstRun = false;
|
||||||
final index = allMediaFiles.indexWhere(
|
await loadCurrentMediaFile();
|
||||||
(m) => m.messageId == msg.messageId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index >= 1) {
|
|
||||||
allMediaFiles[index] = msg;
|
|
||||||
} else if (index == -1) {
|
|
||||||
// If the message does not exist, add it
|
|
||||||
allMediaFiles.add(msg);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
setState(() {});
|
|
||||||
if (firstRun) {
|
|
||||||
firstRun = false;
|
|
||||||
await loadCurrentMediaFile();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
|
@ -30,24 +33,38 @@ class ContactView extends StatefulWidget {
|
||||||
class _ContactViewState extends State<ContactView> {
|
class _ContactViewState extends State<ContactView> {
|
||||||
Contact? _contact;
|
Contact? _contact;
|
||||||
List<GroupMember> _memberOfGroups = [];
|
List<GroupMember> _memberOfGroups = [];
|
||||||
|
List<KeyVerification> _keyVerifications = [];
|
||||||
|
|
||||||
late StreamSubscription<Contact?> _contactSub;
|
late StreamSubscription<(Contact, bool)?> _contactSub;
|
||||||
late StreamSubscription<List<GroupMember>> _groupMemberSub;
|
late StreamSubscription<List<GroupMember>> _groupMemberSub;
|
||||||
|
late StreamSubscription<List<KeyVerification>> _streamKeyVerifications;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_contactSub = twonlyDB.contactsDao.watchContact(widget.userId).listen((
|
_contactSub = twonlyDB.contactsDao
|
||||||
update,
|
.watchContactAndVerificationState(widget.userId)
|
||||||
) {
|
.listen((
|
||||||
setState(() {
|
update,
|
||||||
_contact = update;
|
) {
|
||||||
});
|
if (update != null) {
|
||||||
});
|
setState(() {
|
||||||
|
_contact = update.$1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
_groupMemberSub = twonlyDB.groupsDao
|
_groupMemberSub = twonlyDB.groupsDao
|
||||||
.watchContactGroupMember(widget.userId)
|
.watchContactGroupMember(widget.userId)
|
||||||
.listen((groups) async {
|
.listen((groups) async {
|
||||||
_memberOfGroups = groups;
|
_memberOfGroups = groups;
|
||||||
});
|
});
|
||||||
|
_streamKeyVerifications = twonlyDB.keyVerificationDao
|
||||||
|
.watchContactVerification(widget.userId)
|
||||||
|
.listen((update) {
|
||||||
|
setState(() {
|
||||||
|
Log.info('Verifications: ${update.length}');
|
||||||
|
_keyVerifications = update;
|
||||||
|
});
|
||||||
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +72,7 @@ class _ContactViewState extends State<ContactView> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_contactSub.cancel();
|
_contactSub.cancel();
|
||||||
_groupMemberSub.cancel();
|
_groupMemberSub.cancel();
|
||||||
|
_streamKeyVerifications.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,7 +232,7 @@ class _ContactViewState extends State<ContactView> {
|
||||||
RestoreFlameComp(
|
RestoreFlameComp(
|
||||||
contactId: widget.userId,
|
contactId: widget.userId,
|
||||||
),
|
),
|
||||||
if (!contact.verified)
|
if (_keyVerifications.isEmpty)
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
leading: VerificationBadgeComp(
|
leading: VerificationBadgeComp(
|
||||||
contact: contact,
|
contact: contact,
|
||||||
|
|
@ -226,6 +244,37 @@ class _ContactViewState extends State<ContactView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (_keyVerifications.isNotEmpty)
|
||||||
|
ExpansionTile(
|
||||||
|
shape: const RoundedRectangleBorder(),
|
||||||
|
backgroundColor: context.color.surfaceContainer,
|
||||||
|
collapsedShape: const RoundedRectangleBorder(),
|
||||||
|
leading: Padding(
|
||||||
|
padding: EdgeInsetsGeometry.only(left: 12, right: 12),
|
||||||
|
child: VerificationBadgeComp(
|
||||||
|
contact: contact,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(context.lang.userVerifiedTitle),
|
||||||
|
children: _keyVerifications
|
||||||
|
.map(
|
||||||
|
(kv) => ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: Text(_verificationTypeLabel(context, kv.type)),
|
||||||
|
trailing: Text(
|
||||||
|
DateFormat.yMd(
|
||||||
|
Localizations.localeOf(context).toString(),
|
||||||
|
).format(kv.createdAt),
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.onSurfaceVariant,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
if (userService.currentUser.isUserDiscoveryEnabled)
|
if (userService.currentUser.isUserDiscoveryEnabled)
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.usersViewfinder,
|
icon: FontAwesomeIcons.usersViewfinder,
|
||||||
|
|
@ -279,6 +328,19 @@ class _ContactViewState extends State<ContactView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _verificationTypeLabel(BuildContext context, VerificationType type) {
|
||||||
|
return switch (type) {
|
||||||
|
VerificationType.qrScanned => context.lang.verificationTypeQrScanned,
|
||||||
|
VerificationType.secretQrToken =>
|
||||||
|
context.lang.verificationTypeSecretQrToken,
|
||||||
|
VerificationType.link => context.lang.verificationTypeLink,
|
||||||
|
VerificationType.contactSharedByVerified =>
|
||||||
|
context.lang.verificationTypeContactSharedByVerified,
|
||||||
|
VerificationType.migratedFromOldVersion =>
|
||||||
|
context.lang.verificationTypeMigratedFromOldVersion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> showNicknameChangeDialog(
|
Future<String?> showNicknameChangeDialog(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Contact contact,
|
Contact contact,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
import 'package:twonly/src/utils/avatars.dart';
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.dart';
|
import 'package:twonly/src/utils/qr.dart';
|
||||||
|
|
@ -21,7 +22,7 @@ class PublicProfileView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PublicProfileViewState extends State<PublicProfileView> {
|
class _PublicProfileViewState extends State<PublicProfileView> {
|
||||||
Uint8List? _qrCode;
|
String? _qrCode;
|
||||||
Uint8List? _userAvatar;
|
Uint8List? _userAvatar;
|
||||||
Uint8List? _publicKey;
|
Uint8List? _publicKey;
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
_qrCode = await getProfileQrCodeData();
|
_qrCode = await QrCodeUtils.publicProfileLink();
|
||||||
_userAvatar = await getUserAvatar();
|
_userAvatar = await getUserAvatar();
|
||||||
_publicKey = await getUserPublicKey();
|
_publicKey = await getUserPublicKey();
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
|
|
@ -82,7 +83,7 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: QrImageView.withQr(
|
child: QrImageView.withQr(
|
||||||
qr: QrCode.fromUint8List(
|
qr: QrCode.fromData(
|
||||||
data: _qrCode!,
|
data: _qrCode!,
|
||||||
errorCorrectLevel: QrErrorCorrectLevel.M,
|
errorCorrectLevel: QrErrorCorrectLevel.M,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class _PrivacyViewState extends State<PrivacyView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Freunde finden'),
|
title: Text(context.lang.userDiscoverySettingsTitle),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(Routes.settingsPrivacyUserDiscovery);
|
await context.push(Routes.settingsPrivacyUserDiscovery);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue