handling server messages

This commit is contained in:
otsmr 2026-04-20 01:13:11 +02:00
parent 6517473603
commit f2493a2b56
27 changed files with 13369 additions and 70 deletions

View file

@ -1,13 +1,12 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:twonly/core/frb_generated.dart'; import 'package:twonly/core/frb_generated.dart';
import 'package:twonly/main.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async => RustLib.init()); setUpAll(() async => RustLib.init());
testWidgets('Can call rust function', (tester) async { // testWidgets('Can call rust function', (tester) async {
await tester.pumpWidget(const MyApp()); // await tester.pumpWidget(const MyApp());
expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget); // expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget);
}); // });
} }

View file

@ -22,11 +22,11 @@ class FlutterUserDiscovery {
receivedVersion: receivedVersion, receivedVersion: receivedVersion,
); );
static Future<void> handleUserDiscoveryMessages({ static Future<void> handleNewMessages({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<Uint8List> messages, required List<Uint8List> messages,
}) => RustLib.instance.api }) => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleUserDiscoveryMessages( .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages(
contactId: contactId, contactId: contactId,
messages: messages, messages: messages,
); );
@ -42,7 +42,7 @@ class FlutterUserDiscovery {
publicKey: publicKey, publicKey: publicKey,
); );
static Future<bool> shouldRequestNewMessages({ static Future<Uint8List?> shouldRequestNewMessages({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<int> version, required List<int> version,
}) => RustLib.instance.api }) => RustLib.instance.api

View file

@ -71,7 +71,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.12.0'; String get codegenVersion => '2.12.0';
@override @override
int get rustContentHash => 523281685; int get rustContentHash => -630534473;
static const kDefaultExternalLibraryLoaderConfig = static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig( ExternalLibraryLoaderConfig(
@ -92,7 +92,7 @@ abstract class RustLibApi extends BaseApi {
}); });
Future<void> Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleUserDiscoveryMessages({ crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<Uint8List> messages, required List<Uint8List> messages,
}); });
@ -104,7 +104,7 @@ abstract class RustLibApi extends BaseApi {
required List<int> publicKey, required List<int> publicKey,
}); });
Future<bool> Future<Uint8List?>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({ crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<int> version, required List<int> version,
@ -228,7 +228,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
@override @override
Future<void> Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleUserDiscoveryMessages({ crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<Uint8List> messages, required List<Uint8List> messages,
}) { }) {
@ -250,7 +250,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
decodeErrorData: sse_decode_AnyhowException, decodeErrorData: sse_decode_AnyhowException,
), ),
constMeta: constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleUserDiscoveryMessagesConstMeta, kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessagesConstMeta,
argValues: [contactId, messages], argValues: [contactId, messages],
apiImpl: this, apiImpl: this,
), ),
@ -258,9 +258,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
TaskConstMeta TaskConstMeta
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleUserDiscoveryMessagesConstMeta => get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessagesConstMeta =>
const TaskConstMeta( const TaskConstMeta(
debugName: 'flutter_user_discovery_handle_user_discovery_messages', debugName: 'flutter_user_discovery_handle_new_messages',
argNames: ['contactId', 'messages'], argNames: ['contactId', 'messages'],
); );
@ -305,7 +305,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
@override @override
Future<bool> Future<Uint8List?>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({ crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<int> version, required List<int> version,
@ -324,7 +324,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: sse_decode_bool, decodeSuccessData: sse_decode_opt_list_prim_u_8_strict,
decodeErrorData: sse_decode_AnyhowException, decodeErrorData: sse_decode_AnyhowException,
), ),
constMeta: constMeta:

View file

@ -216,10 +216,10 @@ class UserDiscoveryCallbacks {
await twonlyDB await twonlyDB
.into(twonlyDB.userDiscoveryAnnouncedUsers) .into(twonlyDB.userDiscoveryAnnouncedUsers)
.insertOnConflictUpdate( .insertOnConflictUpdate(
UserDiscoveryAnnouncedUser( UserDiscoveryAnnouncedUsersCompanion(
announcedUserId: announcedUser.userId, announcedUserId: Value(announcedUser.userId),
announcedPublicKey: announcedUser.publicKey, announcedPublicKey: Value(announcedUser.publicKey),
publicId: announcedUser.publicId, publicId: Value(announcedUser.publicId),
), ),
); );

View file

@ -18,4 +18,90 @@ class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
// of this object. // of this object.
// ignore: matching_super_parameters // ignore: matching_super_parameters
UserDiscoveryDao(super.db); UserDiscoveryDao(super.db);
/// 1. Get count for contacts which are in announced but not in the contacts table
/// Returns all users which are not yet in the contacts table but have no data loaded (e.g. Avatar, username and display name)
Future<List<UserDiscoveryAnnouncedUser>>
getNewAnnouncementsWithoutData() async {
final query =
select(userDiscoveryAnnouncedUsers).join([
leftOuterJoin(
contacts,
contacts.userId.equalsExp(
userDiscoveryAnnouncedUsers.announcedUserId,
),
),
])
// Apply filters:
// 1. The user must NOT exist in the contacts table
// 2. The username must be null
..where(
contacts.userId.isNull() &
userDiscoveryAnnouncedUsers.username.isNull(),
);
return (await query.get())
.map((row) => row.readTable(userDiscoveryAnnouncedUsers))
.toList();
}
Future<Map<UserDiscoveryAnnouncedUser, List<(int, DateTime?)>>>
getAnnouncedUsersWithRelations() async {
final query = select(userDiscoveryAnnouncedUsers).join([
innerJoin(
userDiscoveryUserRelations,
userDiscoveryUserRelations.announcedUserId.equalsExp(
userDiscoveryAnnouncedUsers.announcedUserId,
),
),
]);
final rows = await query.get();
final results = <UserDiscoveryAnnouncedUser, List<(int, DateTime?)>>{};
for (final row in rows) {
final user = row.readTable(userDiscoveryAnnouncedUsers);
final relation = row.readTable(userDiscoveryUserRelations);
final relationData = (
relation.fromContactId,
relation.publicKeyVerifiedTimestamp,
);
if (!results.containsKey(user)) {
results[user] = [];
}
results[user]!.add(relationData);
}
return results;
}
Stream<int> watchNewAnnouncementsWithDataCount() {
final countExp = userDiscoveryAnnouncedUsers.announcedUserId.count();
final query = selectOnly(userDiscoveryAnnouncedUsers)
..addColumns([countExp])
..where(
// Filters: Has a username AND has not been shown to the user yet
userDiscoveryAnnouncedUsers.username.isNotNull() &
userDiscoveryAnnouncedUsers.wasShownToTheUser.equals(false) &
userDiscoveryAnnouncedUsers.isHidden.equals(false),
);
return query.watchSingle().map((row) => row.read(countExp) ?? 0);
}
Future<void> updateAnnouncedUser(
int announcedUserId,
UserDiscoveryAnnouncedUsersCompanion updatedValues,
) async {
await (update(
userDiscoveryAnnouncedUsers,
)..where((c) => c.announcedUserId.equals(announcedUserId))).write(
updatedValues,
);
}
} }

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,13 @@ class UserDiscoveryAnnouncedUsers extends Table {
BlobColumn get announcedPublicKey => blob()(); BlobColumn get announcedPublicKey => blob()();
IntColumn get publicId => integer().unique()(); IntColumn get publicId => integer().unique()();
// When a new user got announced this data will be requested without adding the users to the contacts...
TextColumn get username => text().nullable()();
BoolColumn get wasShownToTheUser =>
boolean().withDefault(const Constant(false))();
BoolColumn get isHidden => boolean().withDefault(const Constant(false))();
@override @override
Set<Column> get primaryKey => {announcedUserId}; Set<Column> get primaryKey => {announcedUserId};
} }

View file

@ -72,7 +72,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection); TwonlyDB.forTesting(DatabaseConnection super.connection);
@override @override
int get schemaVersion => 13; int get schemaVersion => 14;
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
return driftDatabase( return driftDatabase(
@ -191,6 +191,20 @@ class TwonlyDB extends _$TwonlyDB {
schema.contacts.mediaSendCounter, schema.contacts.mediaSendCounter,
); );
}, },
from13To14: (m, schema) async {
await m.addColumn(
schema.userDiscoveryAnnouncedUsers,
schema.userDiscoveryAnnouncedUsers.wasShownToTheUser,
);
await m.addColumn(
schema.userDiscoveryAnnouncedUsers,
schema.userDiscoveryAnnouncedUsers.isHidden,
);
await m.addColumn(
schema.userDiscoveryAnnouncedUsers,
schema.userDiscoveryAnnouncedUsers.username,
);
},
)(m, from, to); )(m, from, to);
}, },
); );

View file

@ -9537,11 +9537,55 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
requiredDuringInsert: true, requiredDuringInsert: true,
defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'), defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'),
); );
static const VerificationMeta _usernameMeta = const VerificationMeta(
'username',
);
@override
late final GeneratedColumn<String> username = GeneratedColumn<String>(
'username',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _wasShownToTheUserMeta = const VerificationMeta(
'wasShownToTheUser',
);
@override
late final GeneratedColumn<bool> wasShownToTheUser = GeneratedColumn<bool>(
'was_shown_to_the_user',
aliasedName,
false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("was_shown_to_the_user" IN (0, 1))',
),
defaultValue: const Constant(false),
);
static const VerificationMeta _isHiddenMeta = const VerificationMeta(
'isHidden',
);
@override
late final GeneratedColumn<bool> isHidden = GeneratedColumn<bool>(
'is_hidden',
aliasedName,
false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("is_hidden" IN (0, 1))',
),
defaultValue: const Constant(false),
);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
announcedUserId, announcedUserId,
announcedPublicKey, announcedPublicKey,
publicId, publicId,
username,
wasShownToTheUser,
isHidden,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@ -9583,6 +9627,27 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
} else if (isInserting) { } else if (isInserting) {
context.missing(_publicIdMeta); context.missing(_publicIdMeta);
} }
if (data.containsKey('username')) {
context.handle(
_usernameMeta,
username.isAcceptableOrUnknown(data['username']!, _usernameMeta),
);
}
if (data.containsKey('was_shown_to_the_user')) {
context.handle(
_wasShownToTheUserMeta,
wasShownToTheUser.isAcceptableOrUnknown(
data['was_shown_to_the_user']!,
_wasShownToTheUserMeta,
),
);
}
if (data.containsKey('is_hidden')) {
context.handle(
_isHiddenMeta,
isHidden.isAcceptableOrUnknown(data['is_hidden']!, _isHiddenMeta),
);
}
return context; return context;
} }
@ -9607,6 +9672,18 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
DriftSqlType.int, DriftSqlType.int,
data['${effectivePrefix}public_id'], data['${effectivePrefix}public_id'],
)!, )!,
username: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}username'],
),
wasShownToTheUser: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}was_shown_to_the_user'],
)!,
isHidden: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}is_hidden'],
)!,
); );
} }
@ -9621,10 +9698,16 @@ class UserDiscoveryAnnouncedUser extends DataClass
final int announcedUserId; final int announcedUserId;
final Uint8List announcedPublicKey; final Uint8List announcedPublicKey;
final int publicId; final int publicId;
final String? username;
final bool wasShownToTheUser;
final bool isHidden;
const UserDiscoveryAnnouncedUser({ const UserDiscoveryAnnouncedUser({
required this.announcedUserId, required this.announcedUserId,
required this.announcedPublicKey, required this.announcedPublicKey,
required this.publicId, required this.publicId,
this.username,
required this.wasShownToTheUser,
required this.isHidden,
}); });
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
@ -9632,6 +9715,11 @@ class UserDiscoveryAnnouncedUser extends DataClass
map['announced_user_id'] = Variable<int>(announcedUserId); map['announced_user_id'] = Variable<int>(announcedUserId);
map['announced_public_key'] = Variable<Uint8List>(announcedPublicKey); map['announced_public_key'] = Variable<Uint8List>(announcedPublicKey);
map['public_id'] = Variable<int>(publicId); map['public_id'] = Variable<int>(publicId);
if (!nullToAbsent || username != null) {
map['username'] = Variable<String>(username);
}
map['was_shown_to_the_user'] = Variable<bool>(wasShownToTheUser);
map['is_hidden'] = Variable<bool>(isHidden);
return map; return map;
} }
@ -9640,6 +9728,11 @@ class UserDiscoveryAnnouncedUser extends DataClass
announcedUserId: Value(announcedUserId), announcedUserId: Value(announcedUserId),
announcedPublicKey: Value(announcedPublicKey), announcedPublicKey: Value(announcedPublicKey),
publicId: Value(publicId), publicId: Value(publicId),
username: username == null && nullToAbsent
? const Value.absent()
: Value(username),
wasShownToTheUser: Value(wasShownToTheUser),
isHidden: Value(isHidden),
); );
} }
@ -9654,6 +9747,9 @@ class UserDiscoveryAnnouncedUser extends DataClass
json['announcedPublicKey'], json['announcedPublicKey'],
), ),
publicId: serializer.fromJson<int>(json['publicId']), publicId: serializer.fromJson<int>(json['publicId']),
username: serializer.fromJson<String?>(json['username']),
wasShownToTheUser: serializer.fromJson<bool>(json['wasShownToTheUser']),
isHidden: serializer.fromJson<bool>(json['isHidden']),
); );
} }
@override @override
@ -9663,6 +9759,9 @@ class UserDiscoveryAnnouncedUser extends DataClass
'announcedUserId': serializer.toJson<int>(announcedUserId), 'announcedUserId': serializer.toJson<int>(announcedUserId),
'announcedPublicKey': serializer.toJson<Uint8List>(announcedPublicKey), 'announcedPublicKey': serializer.toJson<Uint8List>(announcedPublicKey),
'publicId': serializer.toJson<int>(publicId), 'publicId': serializer.toJson<int>(publicId),
'username': serializer.toJson<String?>(username),
'wasShownToTheUser': serializer.toJson<bool>(wasShownToTheUser),
'isHidden': serializer.toJson<bool>(isHidden),
}; };
} }
@ -9670,10 +9769,16 @@ class UserDiscoveryAnnouncedUser extends DataClass
int? announcedUserId, int? announcedUserId,
Uint8List? announcedPublicKey, Uint8List? announcedPublicKey,
int? publicId, int? publicId,
Value<String?> username = const Value.absent(),
bool? wasShownToTheUser,
bool? isHidden,
}) => UserDiscoveryAnnouncedUser( }) => UserDiscoveryAnnouncedUser(
announcedUserId: announcedUserId ?? this.announcedUserId, announcedUserId: announcedUserId ?? this.announcedUserId,
announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey, announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey,
publicId: publicId ?? this.publicId, publicId: publicId ?? this.publicId,
username: username.present ? username.value : this.username,
wasShownToTheUser: wasShownToTheUser ?? this.wasShownToTheUser,
isHidden: isHidden ?? this.isHidden,
); );
UserDiscoveryAnnouncedUser copyWithCompanion( UserDiscoveryAnnouncedUser copyWithCompanion(
UserDiscoveryAnnouncedUsersCompanion data, UserDiscoveryAnnouncedUsersCompanion data,
@ -9686,6 +9791,11 @@ class UserDiscoveryAnnouncedUser extends DataClass
? data.announcedPublicKey.value ? data.announcedPublicKey.value
: this.announcedPublicKey, : this.announcedPublicKey,
publicId: data.publicId.present ? data.publicId.value : this.publicId, publicId: data.publicId.present ? data.publicId.value : this.publicId,
username: data.username.present ? data.username.value : this.username,
wasShownToTheUser: data.wasShownToTheUser.present
? data.wasShownToTheUser.value
: this.wasShownToTheUser,
isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden,
); );
} }
@ -9694,7 +9804,10 @@ class UserDiscoveryAnnouncedUser extends DataClass
return (StringBuffer('UserDiscoveryAnnouncedUser(') return (StringBuffer('UserDiscoveryAnnouncedUser(')
..write('announcedUserId: $announcedUserId, ') ..write('announcedUserId: $announcedUserId, ')
..write('announcedPublicKey: $announcedPublicKey, ') ..write('announcedPublicKey: $announcedPublicKey, ')
..write('publicId: $publicId') ..write('publicId: $publicId, ')
..write('username: $username, ')
..write('wasShownToTheUser: $wasShownToTheUser, ')
..write('isHidden: $isHidden')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -9704,6 +9817,9 @@ class UserDiscoveryAnnouncedUser extends DataClass
announcedUserId, announcedUserId,
$driftBlobEquality.hash(announcedPublicKey), $driftBlobEquality.hash(announcedPublicKey),
publicId, publicId,
username,
wasShownToTheUser,
isHidden,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -9714,7 +9830,10 @@ class UserDiscoveryAnnouncedUser extends DataClass
other.announcedPublicKey, other.announcedPublicKey,
this.announcedPublicKey, this.announcedPublicKey,
) && ) &&
other.publicId == this.publicId); other.publicId == this.publicId &&
other.username == this.username &&
other.wasShownToTheUser == this.wasShownToTheUser &&
other.isHidden == this.isHidden);
} }
class UserDiscoveryAnnouncedUsersCompanion class UserDiscoveryAnnouncedUsersCompanion
@ -9722,27 +9841,42 @@ class UserDiscoveryAnnouncedUsersCompanion
final Value<int> announcedUserId; final Value<int> announcedUserId;
final Value<Uint8List> announcedPublicKey; final Value<Uint8List> announcedPublicKey;
final Value<int> publicId; final Value<int> publicId;
final Value<String?> username;
final Value<bool> wasShownToTheUser;
final Value<bool> isHidden;
const UserDiscoveryAnnouncedUsersCompanion({ const UserDiscoveryAnnouncedUsersCompanion({
this.announcedUserId = const Value.absent(), this.announcedUserId = const Value.absent(),
this.announcedPublicKey = const Value.absent(), this.announcedPublicKey = const Value.absent(),
this.publicId = const Value.absent(), this.publicId = const Value.absent(),
this.username = const Value.absent(),
this.wasShownToTheUser = const Value.absent(),
this.isHidden = const Value.absent(),
}); });
UserDiscoveryAnnouncedUsersCompanion.insert({ UserDiscoveryAnnouncedUsersCompanion.insert({
this.announcedUserId = const Value.absent(), this.announcedUserId = const Value.absent(),
required Uint8List announcedPublicKey, required Uint8List announcedPublicKey,
required int publicId, required int publicId,
this.username = const Value.absent(),
this.wasShownToTheUser = const Value.absent(),
this.isHidden = const Value.absent(),
}) : announcedPublicKey = Value(announcedPublicKey), }) : announcedPublicKey = Value(announcedPublicKey),
publicId = Value(publicId); publicId = Value(publicId);
static Insertable<UserDiscoveryAnnouncedUser> custom({ static Insertable<UserDiscoveryAnnouncedUser> custom({
Expression<int>? announcedUserId, Expression<int>? announcedUserId,
Expression<Uint8List>? announcedPublicKey, Expression<Uint8List>? announcedPublicKey,
Expression<int>? publicId, Expression<int>? publicId,
Expression<String>? username,
Expression<bool>? wasShownToTheUser,
Expression<bool>? isHidden,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
if (announcedUserId != null) 'announced_user_id': announcedUserId, if (announcedUserId != null) 'announced_user_id': announcedUserId,
if (announcedPublicKey != null) if (announcedPublicKey != null)
'announced_public_key': announcedPublicKey, 'announced_public_key': announcedPublicKey,
if (publicId != null) 'public_id': publicId, if (publicId != null) 'public_id': publicId,
if (username != null) 'username': username,
if (wasShownToTheUser != null) 'was_shown_to_the_user': wasShownToTheUser,
if (isHidden != null) 'is_hidden': isHidden,
}); });
} }
@ -9750,11 +9884,17 @@ class UserDiscoveryAnnouncedUsersCompanion
Value<int>? announcedUserId, Value<int>? announcedUserId,
Value<Uint8List>? announcedPublicKey, Value<Uint8List>? announcedPublicKey,
Value<int>? publicId, Value<int>? publicId,
Value<String?>? username,
Value<bool>? wasShownToTheUser,
Value<bool>? isHidden,
}) { }) {
return UserDiscoveryAnnouncedUsersCompanion( return UserDiscoveryAnnouncedUsersCompanion(
announcedUserId: announcedUserId ?? this.announcedUserId, announcedUserId: announcedUserId ?? this.announcedUserId,
announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey, announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey,
publicId: publicId ?? this.publicId, publicId: publicId ?? this.publicId,
username: username ?? this.username,
wasShownToTheUser: wasShownToTheUser ?? this.wasShownToTheUser,
isHidden: isHidden ?? this.isHidden,
); );
} }
@ -9772,6 +9912,15 @@ class UserDiscoveryAnnouncedUsersCompanion
if (publicId.present) { if (publicId.present) {
map['public_id'] = Variable<int>(publicId.value); map['public_id'] = Variable<int>(publicId.value);
} }
if (username.present) {
map['username'] = Variable<String>(username.value);
}
if (wasShownToTheUser.present) {
map['was_shown_to_the_user'] = Variable<bool>(wasShownToTheUser.value);
}
if (isHidden.present) {
map['is_hidden'] = Variable<bool>(isHidden.value);
}
return map; return map;
} }
@ -9780,7 +9929,10 @@ class UserDiscoveryAnnouncedUsersCompanion
return (StringBuffer('UserDiscoveryAnnouncedUsersCompanion(') return (StringBuffer('UserDiscoveryAnnouncedUsersCompanion(')
..write('announcedUserId: $announcedUserId, ') ..write('announcedUserId: $announcedUserId, ')
..write('announcedPublicKey: $announcedPublicKey, ') ..write('announcedPublicKey: $announcedPublicKey, ')
..write('publicId: $publicId') ..write('publicId: $publicId, ')
..write('username: $username, ')
..write('wasShownToTheUser: $wasShownToTheUser, ')
..write('isHidden: $isHidden')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@ -19690,12 +19842,18 @@ typedef $$UserDiscoveryAnnouncedUsersTableCreateCompanionBuilder =
Value<int> announcedUserId, Value<int> announcedUserId,
required Uint8List announcedPublicKey, required Uint8List announcedPublicKey,
required int publicId, required int publicId,
Value<String?> username,
Value<bool> wasShownToTheUser,
Value<bool> isHidden,
}); });
typedef $$UserDiscoveryAnnouncedUsersTableUpdateCompanionBuilder = typedef $$UserDiscoveryAnnouncedUsersTableUpdateCompanionBuilder =
UserDiscoveryAnnouncedUsersCompanion Function({ UserDiscoveryAnnouncedUsersCompanion Function({
Value<int> announcedUserId, Value<int> announcedUserId,
Value<Uint8List> announcedPublicKey, Value<Uint8List> announcedPublicKey,
Value<int> publicId, Value<int> publicId,
Value<String?> username,
Value<bool> wasShownToTheUser,
Value<bool> isHidden,
}); });
final class $$UserDiscoveryAnnouncedUsersTableReferences final class $$UserDiscoveryAnnouncedUsersTableReferences
@ -19769,6 +19927,21 @@ class $$UserDiscoveryAnnouncedUsersTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<String> get username => $composableBuilder(
column: $table.username,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get wasShownToTheUser => $composableBuilder(
column: $table.wasShownToTheUser,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get isHidden => $composableBuilder(
column: $table.isHidden,
builder: (column) => ColumnFilters(column),
);
Expression<bool> userDiscoveryUserRelationsRefs( Expression<bool> userDiscoveryUserRelationsRefs(
Expression<bool> Function($$UserDiscoveryUserRelationsTableFilterComposer f) Expression<bool> Function($$UserDiscoveryUserRelationsTableFilterComposer f)
f, f,
@ -19820,6 +19993,21 @@ class $$UserDiscoveryAnnouncedUsersTableOrderingComposer
column: $table.publicId, column: $table.publicId,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get username => $composableBuilder(
column: $table.username,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get wasShownToTheUser => $composableBuilder(
column: $table.wasShownToTheUser,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get isHidden => $composableBuilder(
column: $table.isHidden,
builder: (column) => ColumnOrderings(column),
);
} }
class $$UserDiscoveryAnnouncedUsersTableAnnotationComposer class $$UserDiscoveryAnnouncedUsersTableAnnotationComposer
@ -19844,6 +20032,17 @@ class $$UserDiscoveryAnnouncedUsersTableAnnotationComposer
GeneratedColumn<int> get publicId => GeneratedColumn<int> get publicId =>
$composableBuilder(column: $table.publicId, builder: (column) => column); $composableBuilder(column: $table.publicId, builder: (column) => column);
GeneratedColumn<String> get username =>
$composableBuilder(column: $table.username, builder: (column) => column);
GeneratedColumn<bool> get wasShownToTheUser => $composableBuilder(
column: $table.wasShownToTheUser,
builder: (column) => column,
);
GeneratedColumn<bool> get isHidden =>
$composableBuilder(column: $table.isHidden, builder: (column) => column);
Expression<T> userDiscoveryUserRelationsRefs<T extends Object>( Expression<T> userDiscoveryUserRelationsRefs<T extends Object>(
Expression<T> Function( Expression<T> Function(
$$UserDiscoveryUserRelationsTableAnnotationComposer a, $$UserDiscoveryUserRelationsTableAnnotationComposer a,
@ -19919,20 +20118,32 @@ class $$UserDiscoveryAnnouncedUsersTableTableManager
Value<int> announcedUserId = const Value.absent(), Value<int> announcedUserId = const Value.absent(),
Value<Uint8List> announcedPublicKey = const Value.absent(), Value<Uint8List> announcedPublicKey = const Value.absent(),
Value<int> publicId = const Value.absent(), Value<int> publicId = const Value.absent(),
Value<String?> username = const Value.absent(),
Value<bool> wasShownToTheUser = const Value.absent(),
Value<bool> isHidden = const Value.absent(),
}) => UserDiscoveryAnnouncedUsersCompanion( }) => UserDiscoveryAnnouncedUsersCompanion(
announcedUserId: announcedUserId, announcedUserId: announcedUserId,
announcedPublicKey: announcedPublicKey, announcedPublicKey: announcedPublicKey,
publicId: publicId, publicId: publicId,
username: username,
wasShownToTheUser: wasShownToTheUser,
isHidden: isHidden,
), ),
createCompanionCallback: createCompanionCallback:
({ ({
Value<int> announcedUserId = const Value.absent(), Value<int> announcedUserId = const Value.absent(),
required Uint8List announcedPublicKey, required Uint8List announcedPublicKey,
required int publicId, required int publicId,
Value<String?> username = const Value.absent(),
Value<bool> wasShownToTheUser = const Value.absent(),
Value<bool> isHidden = const Value.absent(),
}) => UserDiscoveryAnnouncedUsersCompanion.insert( }) => UserDiscoveryAnnouncedUsersCompanion.insert(
announcedUserId: announcedUserId, announcedUserId: announcedUserId,
announcedPublicKey: announcedPublicKey, announcedPublicKey: announcedPublicKey,
publicId: publicId, publicId: publicId,
username: username,
wasShownToTheUser: wasShownToTheUser,
isHidden: isHidden,
), ),
withReferenceMapper: (p0) => p0 withReferenceMapper: (p0) => p0
.map( .map(

View file

@ -6932,6 +6932,454 @@ i1.GeneratedColumn<int> _column_229(String aliasedName) =>
$customConstraints: 'NOT NULL DEFAULT 0', $customConstraints: 'NOT NULL DEFAULT 0',
defaultValue: const i1.CustomExpression('0'), defaultValue: const i1.CustomExpression('0'),
); );
final class Schema14 extends i0.VersionedSchema {
Schema14({required super.database}) : super(version: 14);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
groups,
mediaFiles,
messages,
messageHistories,
reactions,
groupMembers,
receipts,
receivedReceipts,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
messageActions,
groupHistories,
keyVerifications,
verificationTokens,
userDiscoveryAnnouncedUsers,
userDiscoveryUserRelations,
userDiscoveryOtherPromotions,
userDiscoveryOwnPromotions,
userDiscoveryShares,
];
late final Shape47 contacts = Shape47(
source: i0.VersionedTable(
entityName: 'contacts',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(user_id)'],
columns: [
_column_106,
_column_107,
_column_108,
_column_109,
_column_110,
_column_111,
_column_112,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_211,
_column_228,
_column_229,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape23 groups = Shape23(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(group_id)'],
columns: [
_column_119,
_column_120,
_column_121,
_column_122,
_column_123,
_column_124,
_column_125,
_column_126,
_column_127,
_column_128,
_column_129,
_column_130,
_column_131,
_column_132,
_column_133,
_column_134,
_column_118,
_column_135,
_column_136,
_column_137,
_column_138,
_column_139,
_column_140,
_column_141,
_column_142,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape36 mediaFiles = Shape36(
source: i0.VersionedTable(
entityName: 'media_files',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(media_id)'],
columns: [
_column_143,
_column_144,
_column_145,
_column_146,
_column_147,
_column_148,
_column_149,
_column_207,
_column_150,
_column_151,
_column_152,
_column_153,
_column_154,
_column_155,
_column_156,
_column_157,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape25 messages = Shape25(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(message_id)'],
columns: [
_column_158,
_column_159,
_column_160,
_column_144,
_column_161,
_column_162,
_column_163,
_column_164,
_column_165,
_column_153,
_column_166,
_column_167,
_column_168,
_column_169,
_column_118,
_column_170,
_column_171,
_column_172,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape26 messageHistories = Shape26(
source: i0.VersionedTable(
entityName: 'message_histories',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_173,
_column_174,
_column_175,
_column_161,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape27 reactions = Shape27(
source: i0.VersionedTable(
entityName: 'reactions',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
columns: [_column_174, _column_176, _column_177, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape38 groupMembers = Shape38(
source: i0.VersionedTable(
entityName: 'group_members',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
columns: [
_column_158,
_column_178,
_column_179,
_column_180,
_column_209,
_column_210,
_column_181,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape37 receipts = Shape37(
source: i0.VersionedTable(
entityName: 'receipts',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(receipt_id)'],
columns: [
_column_182,
_column_183,
_column_184,
_column_185,
_column_186,
_column_208,
_column_187,
_column_188,
_column_189,
_column_190,
_column_191,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape30 receivedReceipts = Shape30(
source: i0.VersionedTable(
entityName: 'received_receipts',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(receipt_id)'],
columns: [_column_182, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape31 signalIdentityKeyStores = Shape31(
source: i0.VersionedTable(
entityName: 'signal_identity_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(device_id, name)'],
columns: [_column_192, _column_193, _column_194, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape32 signalPreKeyStores = Shape32(
source: i0.VersionedTable(
entityName: 'signal_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
columns: [_column_195, _column_196, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape11 signalSenderKeyStores = Shape11(
source: i0.VersionedTable(
entityName: 'signal_sender_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
columns: [_column_197, _column_198],
attachedDatabase: database,
),
alias: null,
);
late final Shape33 signalSessionStores = Shape33(
source: i0.VersionedTable(
entityName: 'signal_session_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(device_id, name)'],
columns: [_column_192, _column_193, _column_199, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape34 messageActions = Shape34(
source: i0.VersionedTable(
entityName: 'message_actions',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
columns: [_column_174, _column_183, _column_144, _column_200],
attachedDatabase: database,
),
alias: null,
);
late final Shape35 groupHistories = Shape35(
source: i0.VersionedTable(
entityName: 'group_histories',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(group_history_id)'],
columns: [
_column_201,
_column_158,
_column_202,
_column_203,
_column_204,
_column_205,
_column_206,
_column_144,
_column_200,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape40 keyVerifications = Shape40(
source: i0.VersionedTable(
entityName: 'key_verifications',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(contact_id)'],
columns: [_column_183, _column_144, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape41 verificationTokens = Shape41(
source: i0.VersionedTable(
entityName: 'verification_tokens',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_212, _column_213, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape48 userDiscoveryAnnouncedUsers = Shape48(
source: i0.VersionedTable(
entityName: 'user_discovery_announced_users',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
columns: [
_column_214,
_column_215,
_column_216,
_column_230,
_column_231,
_column_232,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape43 userDiscoveryUserRelations = Shape43(
source: i0.VersionedTable(
entityName: 'user_discovery_user_relations',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
columns: [_column_217, _column_218, _column_219],
attachedDatabase: database,
),
alias: null,
);
late final Shape44 userDiscoveryOtherPromotions = Shape44(
source: i0.VersionedTable(
entityName: 'user_discovery_other_promotions',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(from_contact_id, promotion_id)'],
columns: [
_column_218,
_column_220,
_column_221,
_column_222,
_column_223,
_column_219,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape45 userDiscoveryOwnPromotions = Shape45(
source: i0.VersionedTable(
entityName: 'user_discovery_own_promotions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_224, _column_183, _column_225],
attachedDatabase: database,
),
alias: null,
);
late final Shape46 userDiscoveryShares = Shape46(
source: i0.VersionedTable(
entityName: 'user_discovery_shares',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_226, _column_227, _column_175],
attachedDatabase: database,
),
alias: null,
);
}
class Shape48 extends i0.VersionedTable {
Shape48({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get announcedUserId =>
columnsByName['announced_user_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<i2.Uint8List> get announcedPublicKey =>
columnsByName['announced_public_key']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get publicId =>
columnsByName['public_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get username =>
columnsByName['username']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get wasShownToTheUser =>
columnsByName['was_shown_to_the_user']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get isHidden =>
columnsByName['is_hidden']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<String> _column_230(String aliasedName) =>
i1.GeneratedColumn<String>(
'username',
aliasedName,
true,
type: i1.DriftSqlType.string,
$customConstraints: 'NULL',
);
i1.GeneratedColumn<int> _column_231(String aliasedName) =>
i1.GeneratedColumn<int>(
'was_shown_to_the_user',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints:
'NOT NULL DEFAULT 0 CHECK (was_shown_to_the_user IN (0, 1))',
defaultValue: const i1.CustomExpression('0'),
);
i1.GeneratedColumn<int> _column_232(String aliasedName) =>
i1.GeneratedColumn<int>(
'is_hidden',
aliasedName,
false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL DEFAULT 0 CHECK (is_hidden IN (0, 1))',
defaultValue: const i1.CustomExpression('0'),
);
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -6945,6 +7393,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11, required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12, required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13, required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@ -7008,6 +7457,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from12To13(migrator, schema); await from12To13(migrator, schema);
return 13; return 13;
case 13:
final schema = Schema14(database: database);
final migrator = i1.Migrator(database, schema);
await from13To14(migrator, schema);
return 14;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@ -7027,6 +7481,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11, required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12, required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13, required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
}) => i0.VersionedSchema.stepByStepHelper( }) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
from1To2: from1To2, from1To2: from1To2,
@ -7041,5 +7496,6 @@ i1.OnUpgrade stepByStep({
from10To11: from10To11, from10To11: from10To11,
from11To12: from11To12, from11To12: from11To12,
from12To13: from12To13, from12To13: from12To13,
from13To14: from13To14,
), ),
); );

View file

@ -3141,6 +3141,12 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'When the typing indicator is turned off, you can\'t see when others are typing a message.'** /// **'When the typing indicator is turned off, you can\'t see when others are typing a message.'**
String get settingsTypingIndicationSubtitle; String get settingsTypingIndicationSubtitle;
/// No description provided for @scanQrOrShow.
///
/// In en, this message translates to:
/// **'Scan / Show QR'**
String get scanQrOrShow;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View file

@ -1761,4 +1761,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settingsTypingIndicationSubtitle => String get settingsTypingIndicationSubtitle =>
'Bei deaktivierten Tipp-Indikatoren kannst du nicht sehen, wenn andere gerade eine Nachricht tippen.'; 'Bei deaktivierten Tipp-Indikatoren kannst du nicht sehen, wenn andere gerade eine Nachricht tippen.';
@override
String get scanQrOrShow => 'QR scannen / anzeigen';
} }

View file

@ -1749,4 +1749,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settingsTypingIndicationSubtitle => String get settingsTypingIndicationSubtitle =>
'When the typing indicator is turned off, you can\'t see when others are typing a message.'; 'When the typing indicator is turned off, you can\'t see when others are typing a message.';
@override
String get scanQrOrShow => 'Scan / Show QR';
} }

View file

@ -1749,4 +1749,7 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get settingsTypingIndicationSubtitle => String get settingsTypingIndicationSubtitle =>
'When the typing indicator is turned off, you can\'t see when others are typing a message.'; 'When the typing indicator is turned off, you can\'t see when others are typing a message.';
@override
String get scanQrOrShow => 'Scan / Show QR';
} }

View file

@ -37,6 +37,7 @@ import 'package:twonly/src/services/notifications/pushkeys.notifications.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/utils.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/services/subscription.service.dart';
import 'package:twonly/src/services/user_discovery.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';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -100,6 +101,7 @@ class ApiService {
unawaited(retransmitAllMessages()); unawaited(retransmitAllMessages());
unawaited(tryDownloadAllMediaFiles()); unawaited(tryDownloadAllMediaFiles());
unawaited(reuploadMediaFiles()); unawaited(reuploadMediaFiles());
twonlyDB.markUpdated(); twonlyDB.markUpdated();
unawaited(syncFlameCounters()); unawaited(syncFlameCounters());
unawaited(setupNotificationWithUsers()); unawaited(setupNotificationWithUsers());
@ -108,6 +110,8 @@ class ApiService {
unawaited(fetchMissingGroupPublicKey()); unawaited(fetchMissingGroupPublicKey());
unawaited(checkForDeletedUsernames()); unawaited(checkForDeletedUsernames());
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
if (gUser.userStudyParticipantsToken != null) { if (gUser.userStudyParticipantsToken != null) {
// In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection // In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection
unawaited(handleUserStudyUpload()); unawaited(handleUserStudyUpload());

View file

@ -0,0 +1,78 @@
import 'dart:typed_data';
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/user_discovery.service.dart';
import 'package:twonly/src/utils/log.dart';
Future<void> checkForUserDiscoveryChanges(
int fromUserId,
List<int> receivedVersion,
) async {
final currentVersion = await UserDiscoveryService.shouldRequestNewMessages(
fromUserId,
receivedVersion,
);
if (currentVersion != null) {
await sendCipherText(
fromUserId,
EncryptedContent(
userDiscoveryRequest: EncryptedContent_UserDiscoveryRequest(
currentVersion: currentVersion.toList(),
),
),
);
}
}
Future<void> handleUserDiscoveryRequest(
int fromUserId,
EncryptedContent_UserDiscoveryRequest request,
) async {
if (!gUser.isUserDiscoveryEnabled) {
Log.warn('Got a user discovery request while it is disabled');
return;
}
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
if (contact == null) return;
if (contact.mediaSendCounter < gUser.minimumRequiredImagesExchanged) {
Log.warn(
'Got a request to update user discovery, but mediaSendCounter (${contact.mediaSendCounter}) < ${gUser.minimumRequiredImagesExchanged}',
);
return;
}
final newMessages = await UserDiscoveryService.getNewMessages(
fromUserId,
request.currentVersion,
);
if (newMessages != null && newMessages.isNotEmpty) {
await sendCipherText(
fromUserId,
EncryptedContent(
userDiscoveryUpdate: EncryptedContent_UserDiscoveryUpdate(
messages: newMessages,
),
),
);
} else {
Log.info('Got update request, but there are no new updates for the user');
}
}
Future<void> handleUserDiscoveryUpdate(
int fromUserId,
EncryptedContent_UserDiscoveryUpdate update,
) async {
if (!gUser.isUserDiscoveryEnabled) {
Log.warn('Got a user discovery update while it is disabled');
return;
}
await UserDiscoveryService.handleNewMessages(
fromUserId,
update.messages.map(Uint8List.fromList).toList(),
);
}

View file

@ -18,6 +18,7 @@ import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.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';
import 'package:twonly/src/services/user_discovery.service.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';
@ -344,6 +345,17 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
} }
encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter); encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter);
if (gUser.isUserDiscoveryEnabled) {
final contact = await twonlyDB.contactsDao.getContactById(contactId);
if (contact != null &&
contact.mediaSendCounter >= gUser.minimumRequiredImagesExchanged) {
final version = await UserDiscoveryService.getCurrentVersion();
if (version != null) {
encryptedContent.senderUserDiscoveryVersion = version;
}
}
}
final response = pb.Message() final response = pb.Message()
..type = pb.Message_Type.CIPHERTEXT ..type = pb.Message_Type.CIPHERTEXT
..encryptedContent = encryptedContent.writeToBuffer(); ..encryptedContent = encryptedContent.writeToBuffer();

View file

@ -24,6 +24,7 @@ import 'package:twonly/src/services/api/client2client/prekeys.c2c.dart';
import 'package:twonly/src/services/api/client2client/pushkeys.c2c.dart'; import 'package:twonly/src/services/api/client2client/pushkeys.c2c.dart';
import 'package:twonly/src/services/api/client2client/reaction.c2c.dart'; import 'package:twonly/src/services/api/client2client/reaction.c2c.dart';
import 'package:twonly/src/services/api/client2client/text_message.c2c.dart'; 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/messages.dart'; import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/services/group.services.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart';
@ -262,6 +263,12 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
await twonlyDB.receiptsDao.markMessagesForRetry(fromUserId); await twonlyDB.receiptsDao.markMessagesForRetry(fromUserId);
final senderProfileCounter = await checkForProfileUpdate(fromUserId, content); final senderProfileCounter = await checkForProfileUpdate(fromUserId, content);
if (gUser.isUserDiscoveryEnabled && content.hasSenderUserDiscoveryVersion()) {
await checkForUserDiscoveryChanges(
fromUserId,
content.senderUserDiscoveryVersion,
);
}
if (content.hasContactRequest()) { if (content.hasContactRequest()) {
if (!await handleContactRequest(fromUserId, content.contactRequest)) { if (!await handleContactRequest(fromUserId, content.contactRequest)) {
@ -291,6 +298,22 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
return (null, null); return (null, null);
} }
if (content.hasUserDiscoveryRequest()) {
await handleUserDiscoveryRequest(
fromUserId,
content.userDiscoveryRequest,
);
return (null, null);
}
if (content.hasUserDiscoveryUpdate()) {
await handleUserDiscoveryUpdate(
fromUserId,
content.userDiscoveryUpdate,
);
return (null, null);
}
if (content.hasPushKeys()) { if (content.hasPushKeys()) {
await handlePushKey(fromUserId, content.pushKeys); await handlePushKey(fromUserId, content.pushKeys);
return (null, null); return (null, null);

View file

@ -1,13 +1,43 @@
import 'dart:typed_data'; import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:twonly/core/bridge/wrapper/user_discovery.dart'; import 'package:twonly/core/bridge/wrapper/user_discovery.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.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/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/qr.dart'; import 'package:twonly/src/utils/qr.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
class UserDiscoveryService { class UserDiscoveryService {
static Future<void> checkForNewAnnouncedUsers() async {
final announcedUsers = await twonlyDB.userDiscoveryDao
.getNewAnnouncementsWithoutData();
for (final announcedUser in announcedUsers) {
final userdata = await apiService.getUserById(
announcedUser.announcedUserId,
);
if (userdata == null) continue;
if (userdata.publicIdentityKey !=
announcedUser.announcedPublicKey.toList()) {
Log.error(
'Server delivered a different public key then received from the announcement.',
);
continue;
}
Log.info('Updating the username from the announced user');
// Updating the username, so the data will not be requested again..
await twonlyDB.userDiscoveryDao.updateAnnouncedUser(
announcedUser.announcedUserId,
UserDiscoveryAnnouncedUsersCompanion(
username: Value(utf8.decode(userdata.username)),
),
);
}
}
static Future<void> initializeOrUpdate({ static Future<void> initializeOrUpdate({
required int threshold, required int threshold,
required int minimumRequiredImagesExchanged, required int minimumRequiredImagesExchanged,
@ -44,6 +74,50 @@ class UserDiscoveryService {
return UserDiscoveryVersion.fromBuffer(version); return UserDiscoveryVersion.fromBuffer(version);
} }
static Future<Uint8List?> shouldRequestNewMessages(
int fromUserId,
List<int> receivedVersion,
) async {
try {
return await FlutterUserDiscovery.shouldRequestNewMessages(
contactId: fromUserId,
version: receivedVersion,
);
} catch (e) {
Log.error(e);
return null;
}
}
static Future<List<Uint8List>?> getNewMessages(
int fromUserId,
List<int> receivedVersion,
) async {
try {
return await FlutterUserDiscovery.getNewMessages(
contactId: fromUserId,
receivedVersion: receivedVersion,
);
} catch (e) {
Log.error(e);
return null;
}
}
static Future<void> handleNewMessages(
int fromUserId,
List<Uint8List> messages,
) async {
try {
return await FlutterUserDiscovery.handleNewMessages(
contactId: fromUserId,
messages: messages,
);
} catch (e) {
Log.error(e);
}
}
static Future<void> disable() async { static Future<void> disable() async {
await updateUserdata((u) { await updateUserdata((u) {
u.isUserDiscoveryEnabled = false; u.isUserDiscoveryEnabled = false;

View file

@ -167,17 +167,17 @@ class _SearchUsernameView extends State<AddNewUserView> {
), ),
), ),
), ),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () =>
context.push(Routes.settingsPublicProfile),
icon: const FaIcon(FontAwesomeIcons.qrcode),
),
),
], ],
), ),
), ),
const SizedBox(
height: 20,
),
OutlinedButton.icon(
onPressed: () => context.push(Routes.settingsPublicProfile),
icon: const FaIcon(FontAwesomeIcons.qrcode),
label: Text(context.lang.scanQrOrShow),
),
const SizedBox(height: 20), const SizedBox(height: 20),
if (contacts.isNotEmpty) if (contacts.isNotEmpty)
HeadLineComponent( HeadLineComponent(

View file

@ -34,6 +34,11 @@ class _ChatListViewState extends State<ChatListView> {
GlobalKey searchForOtherUsers = GlobalKey(); GlobalKey searchForOtherUsers = GlobalKey();
bool showFeedbackShortcut = false; bool showFeedbackShortcut = false;
int _countContactRequest = 0;
int _countAnnouncedUsers = 0;
late StreamSubscription<int?> _countContactRequestStream;
late StreamSubscription<int?> _countAnnouncedStream;
@override @override
void initState() { void initState() {
initAsync(); initAsync();
@ -52,6 +57,24 @@ class _ChatListViewState extends State<ChatListView> {
}); });
}); });
_countContactRequestStream = twonlyDB.contactsDao
.watchContactsRequestedCount()
.listen((update) {
if (update != null) {
setState(() {
_countContactRequest = update;
});
}
});
_countContactRequestStream = twonlyDB.userDiscoveryDao
.watchNewAnnouncementsWithDataCount()
.listen((update) {
setState(() {
_countAnnouncedUsers = update;
});
});
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final changeLog = await rootBundle.loadString('CHANGELOG.md'); final changeLog = await rootBundle.loadString('CHANGELOG.md');
final changeLogHash = (await compute( final changeLogHash = (await compute(
@ -80,6 +103,8 @@ class _ChatListViewState extends State<ChatListView> {
@override @override
void dispose() { void dispose() {
_contactsSub.cancel(); _contactsSub.cancel();
_countContactRequestStream.cancel();
_countAnnouncedStream.cancel();
super.dispose(); super.dispose();
} }
@ -132,23 +157,38 @@ class _ChatListViewState extends State<ChatListView> {
), ),
actions: [ actions: [
const FeedbackIconButton(), const FeedbackIconButton(),
StreamBuilder( Stack(
stream: twonlyDB.contactsDao.watchContactsRequestedCount(), children: [
builder: (context, snapshot) { if (_countAnnouncedUsers + _countContactRequest > 0)
var count = 0; Positioned.fill(
if (snapshot.hasData && snapshot.data != null) { child: Center(
count = snapshot.data!; child: Container(
} width: 40,
return NotificationBadge( height: 40,
count: count.toString(), decoration: BoxDecoration(
child: IconButton( color: context.color.primary,
key: searchForOtherUsers, shape: BoxShape.circle,
icon: const FaIcon(FontAwesomeIcons.userPlus, size: 18), ),
onPressed: () => context.push(Routes.chatsAddNewUser), ),
),
), ),
); Center(
}, child: NotificationBadge(
count: (_countAnnouncedUsers + _countContactRequest)
.toString(),
child: IconButton(
color: (_countAnnouncedUsers + _countContactRequest > 0)
? Colors.black
: null,
key: searchForOtherUsers,
icon: const FaIcon(FontAwesomeIcons.userPlus, size: 18),
onPressed: () => context.push(Routes.chatsAddNewUser),
),
),
),
],
), ),
IconButton( IconButton(
onPressed: () async { onPressed: () async {
await context.push(Routes.settings); await context.push(Routes.settings);

View file

@ -38,7 +38,10 @@ impl FlutterUserDiscovery {
.await?) .await?)
} }
pub async fn should_request_new_messages(contact_id: i64, version: &[u8]) -> Result<bool> { pub async fn should_request_new_messages(
contact_id: i64,
version: &[u8],
) -> Result<Option<Vec<u8>>> {
Ok(get_twonly_flutter()? Ok(get_twonly_flutter()?
.user_discovery .user_discovery
.get() .get()
@ -47,15 +50,12 @@ impl FlutterUserDiscovery {
.await?) .await?)
} }
pub async fn handle_user_discovery_messages( pub async fn handle_new_messages(contact_id: i64, messages: Vec<Vec<u8>>) -> Result<()> {
contact_id: i64,
messages: Vec<Vec<u8>>,
) -> Result<()> {
Ok(get_twonly_flutter()? Ok(get_twonly_flutter()?
.user_discovery .user_discovery
.get() .get()
.await .await
.handle_user_discovery_messages(contact_id, messages) .handle_new_messages(contact_id, messages)
.await?) .await?)
} }
} }

View file

@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueMoi, default_rust_auto_opaque = RustAutoOpaqueMoi,
); );
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 523281685; pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -630534473;
// Section: executor // Section: executor
@ -77,19 +77,19 @@ let api_received_version = <Vec<u8>>::sse_decode(&mut deserializer);deserializer
})().await) })().await)
} }) } })
} }
fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_user_discovery_messages_impl( fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_new_messages_impl(
port_: flutter_rust_bridge::for_generated::MessagePort, port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32, rust_vec_len_: i32,
data_len_: i32, data_len_: i32,
) { ) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_handle_user_discovery_messages", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || { FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_handle_new_messages", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) }; let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_contact_id = <i64>::sse_decode(&mut deserializer); let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_messages = <Vec<Vec<u8>>>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { let api_messages = <Vec<Vec<u8>>>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::handle_user_discovery_messages(api_contact_id, api_messages).await?; Ok(output_ok) let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::handle_new_messages(api_contact_id, api_messages).await?; Ok(output_ok)
})().await) })().await)
} }) } })
} }
@ -900,7 +900,7 @@ fn pde_ffi_dispatcher_primary_impl(
match func_id { match func_id {
1 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl(port, ptr, rust_vec_len, data_len), 1 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_current_version_impl(port, ptr, rust_vec_len, data_len),
2 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_messages_impl(port, ptr, rust_vec_len, data_len), 2 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_messages_impl(port, ptr, rust_vec_len, data_len),
3 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_user_discovery_messages_impl(port, ptr, rust_vec_len, data_len), 3 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_new_messages_impl(port, ptr, rust_vec_len, data_len),
4 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initialize_or_update_impl(port, ptr, rust_vec_len, data_len), 4 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initialize_or_update_impl(port, ptr, rust_vec_len, data_len),
5 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_request_new_messages_impl(port, ptr, rust_vec_len, data_len), 5 => wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_request_new_messages_impl(port, ptr, rust_vec_len, data_len),
6 => wire__crate__bridge__callbacks__init_flutter_callbacks_impl(port, ptr, rust_vec_len, data_len), 6 => wire__crate__bridge__callbacks__init_flutter_callbacks_impl(port, ptr, rust_vec_len, data_len),

View file

@ -231,7 +231,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
&self, &self,
contact_id: UserID, contact_id: UserID,
version: &[u8], version: &[u8],
) -> Result<bool> { ) -> Result<Option<Vec<u8>>> {
let received_version = UserDiscoveryVersion::decode(version)?; let received_version = UserDiscoveryVersion::decode(version)?;
let stored_version = match self.store.get_contact_version(contact_id).await? { let stored_version = match self.store.get_contact_version(contact_id).await? {
Some(buf) => UserDiscoveryVersion::decode(buf.as_slice())?, Some(buf) => UserDiscoveryVersion::decode(buf.as_slice())?,
@ -247,8 +247,13 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
stored.promotion = %stored_version.promotion, stored.promotion = %stored_version.promotion,
"Comparing version numbers" "Comparing version numbers"
); );
Ok(received_version.announcement > stored_version.announcement if received_version.announcement > stored_version.announcement
|| received_version.promotion > stored_version.promotion) || received_version.promotion > stored_version.promotion
{
Ok(Some(stored_version.encode_to_vec()))
} else {
Ok(None)
}
} }
pub(crate) async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> { pub(crate) async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
@ -257,7 +262,7 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
/// Returns the latest version for this discovery. /// Returns the latest version for this discovery.
/// Before calling this function the application must sure that contact_id is qualified to be announced. /// Before calling this function the application must sure that contact_id is qualified to be announced.
pub async fn handle_user_discovery_messages( pub async fn handle_new_messages(
&self, &self,
contact_id: UserID, contact_id: UserID,
messages: Vec<Vec<u8>>, messages: Vec<Vec<u8>>,

View file

@ -38,7 +38,8 @@ async fn assert_new_messages<S: UserDiscoveryStore>(
assert_eq!( assert_eq!(
to.1.should_request_new_messages(from.0 as UserID, to_received_version) to.1.should_request_new_messages(from.0 as UserID, to_received_version)
.await .await
.unwrap(), .unwrap()
.is_some(),
has_new_messages has_new_messages
); );
} }
@ -53,7 +54,8 @@ async fn request_and_handle_messages<S: UserDiscoveryStore>(
assert_eq!( assert_eq!(
to.1.should_request_new_messages(from.0 as UserID, to_received_version) to.1.should_request_new_messages(from.0 as UserID, to_received_version)
.await .await
.unwrap(), .unwrap()
.is_some(),
true true
); );
@ -72,7 +74,7 @@ async fn request_and_handle_messages<S: UserDiscoveryStore>(
assert!(new_messages.len() <= messages_count); assert!(new_messages.len() <= messages_count);
to.1.handle_user_discovery_messages(from.0 as UserID, new_messages) to.1.handle_new_messages(from.0 as UserID, new_messages)
.await .await
.unwrap(); .unwrap();
@ -82,7 +84,8 @@ async fn request_and_handle_messages<S: UserDiscoveryStore>(
&from.1.get_current_version().await.unwrap() &from.1.get_current_version().await.unwrap()
) )
.await .await
.unwrap(), .unwrap()
.is_some(),
false false
); );
} }

View file

@ -17,6 +17,7 @@ import 'schema_v10.dart' as v10;
import 'schema_v11.dart' as v11; import 'schema_v11.dart' as v11;
import 'schema_v12.dart' as v12; import 'schema_v12.dart' as v12;
import 'schema_v13.dart' as v13; import 'schema_v13.dart' as v13;
import 'schema_v14.dart' as v14;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -48,10 +49,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v12.DatabaseAtV12(db); return v12.DatabaseAtV12(db);
case 13: case 13:
return v13.DatabaseAtV13(db); return v13.DatabaseAtV13(db);
case 14:
return v14.DatabaseAtV14(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
} }
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
} }

File diff suppressed because it is too large Load diff