Promotion of sharing contacts when contact is new to twonly

This commit is contained in:
otsmr 2026-06-16 00:19:40 +02:00
parent 52489a3a98
commit a7414c6fcc
27 changed files with 14941 additions and 138 deletions

View file

@ -1,5 +1,16 @@
# Changelog # Changelog
## 0.3.1
- New: Promotion of sharing contacts when contact is new to twonly
- Fix: Suppressed link previews for scanned QR codes
- Improve: Onboarding of new users to the verification badges
- Improve: Better feedback when a QR code is scanned
- Fix: Black screen on iOS when a link is clicked
- Fix: Fixed size of the typing indicator to prevent the chat from glitching
- Fix: Push notifications are not shown for chats that are already open
- Fix: Multiple smaller performance and UI issues
## 0.3.0 ## 0.3.0
- Improved: Design of some UI components - Improved: Design of some UI components

View file

@ -35,6 +35,6 @@ class AppState {
static bool isInBackgroundTask = false; static bool isInBackgroundTask = false;
static bool allowErrorTrackingViaSentry = false; static bool allowErrorTrackingViaSentry = false;
static bool gotMessageFromServer = false; static bool gotMessageFromServer = false;
static int latestAppVersionId = 116; static int latestAppVersionId = 117;
static bool hasCameraPermissions = false; static bool hasCameraPermissions = false;
} }

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,8 @@ class Contacts extends Table {
BoolColumn get userDiscoveryExcluded => BoolColumn get userDiscoveryExcluded =>
boolean().withDefault(const Constant(false))(); boolean().withDefault(const Constant(false))();
BoolColumn get askForFriendPromotions => boolean().nullable()();
BoolColumn get userDiscoveryManualApproved => BoolColumn get userDiscoveryManualApproved =>
boolean().nullable().withDefault(const Constant(false))(); boolean().nullable().withDefault(const Constant(false))();

View file

@ -82,7 +82,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection); TwonlyDB.forTesting(DatabaseConnection super.connection);
@override @override
int get schemaVersion => 17; int get schemaVersion => 18;
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
final connection = driftDatabase( final connection = driftDatabase(
@ -233,6 +233,12 @@ class TwonlyDB extends _$TwonlyDB {
schema.userDiscoveryAnnouncedUsers.wasAskedFriends, schema.userDiscoveryAnnouncedUsers.wasAskedFriends,
); );
}, },
from17To18: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.askForFriendPromotions,
);
},
)(m, from, to); )(m, from, to);
}, },
); );

View file

@ -200,6 +200,20 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
), ),
defaultValue: const Constant(false), defaultValue: const Constant(false),
); );
static const VerificationMeta _askForFriendPromotionsMeta =
const VerificationMeta('askForFriendPromotions');
@override
late final GeneratedColumn<bool> askForFriendPromotions =
GeneratedColumn<bool>(
'ask_for_friend_promotions',
aliasedName,
true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("ask_for_friend_promotions" IN (0, 1))',
),
);
static const VerificationMeta _userDiscoveryManualApprovedMeta = static const VerificationMeta _userDiscoveryManualApprovedMeta =
const VerificationMeta('userDiscoveryManualApproved'); const VerificationMeta('userDiscoveryManualApproved');
@override @override
@ -255,6 +269,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
createdAt, createdAt,
userDiscoveryVersion, userDiscoveryVersion,
userDiscoveryExcluded, userDiscoveryExcluded,
askForFriendPromotions,
userDiscoveryManualApproved, userDiscoveryManualApproved,
mediaSendCounter, mediaSendCounter,
mediaReceivedCounter, mediaReceivedCounter,
@ -384,6 +399,15 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
), ),
); );
} }
if (data.containsKey('ask_for_friend_promotions')) {
context.handle(
_askForFriendPromotionsMeta,
askForFriendPromotions.isAcceptableOrUnknown(
data['ask_for_friend_promotions']!,
_askForFriendPromotionsMeta,
),
);
}
if (data.containsKey('user_discovery_manual_approved')) { if (data.containsKey('user_discovery_manual_approved')) {
context.handle( context.handle(
_userDiscoveryManualApprovedMeta, _userDiscoveryManualApprovedMeta,
@ -480,6 +504,10 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
DriftSqlType.bool, DriftSqlType.bool,
data['${effectivePrefix}user_discovery_excluded'], data['${effectivePrefix}user_discovery_excluded'],
)!, )!,
askForFriendPromotions: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}ask_for_friend_promotions'],
),
userDiscoveryManualApproved: attachedDatabase.typeMapping.read( userDiscoveryManualApproved: attachedDatabase.typeMapping.read(
DriftSqlType.bool, DriftSqlType.bool,
data['${effectivePrefix}user_discovery_manual_approved'], data['${effectivePrefix}user_discovery_manual_approved'],
@ -517,6 +545,7 @@ class Contact extends DataClass implements Insertable<Contact> {
final DateTime createdAt; final DateTime createdAt;
final Uint8List? userDiscoveryVersion; final Uint8List? userDiscoveryVersion;
final bool userDiscoveryExcluded; final bool userDiscoveryExcluded;
final bool? askForFriendPromotions;
final bool? userDiscoveryManualApproved; final bool? userDiscoveryManualApproved;
final int mediaSendCounter; final int mediaSendCounter;
final int mediaReceivedCounter; final int mediaReceivedCounter;
@ -536,6 +565,7 @@ class Contact extends DataClass implements Insertable<Contact> {
required this.createdAt, required this.createdAt,
this.userDiscoveryVersion, this.userDiscoveryVersion,
required this.userDiscoveryExcluded, required this.userDiscoveryExcluded,
this.askForFriendPromotions,
this.userDiscoveryManualApproved, this.userDiscoveryManualApproved,
required this.mediaSendCounter, required this.mediaSendCounter,
required this.mediaReceivedCounter, required this.mediaReceivedCounter,
@ -566,6 +596,9 @@ class Contact extends DataClass implements Insertable<Contact> {
map['user_discovery_version'] = Variable<Uint8List>(userDiscoveryVersion); map['user_discovery_version'] = Variable<Uint8List>(userDiscoveryVersion);
} }
map['user_discovery_excluded'] = Variable<bool>(userDiscoveryExcluded); map['user_discovery_excluded'] = Variable<bool>(userDiscoveryExcluded);
if (!nullToAbsent || askForFriendPromotions != null) {
map['ask_for_friend_promotions'] = Variable<bool>(askForFriendPromotions);
}
if (!nullToAbsent || userDiscoveryManualApproved != null) { if (!nullToAbsent || userDiscoveryManualApproved != null) {
map['user_discovery_manual_approved'] = Variable<bool>( map['user_discovery_manual_approved'] = Variable<bool>(
userDiscoveryManualApproved, userDiscoveryManualApproved,
@ -601,6 +634,9 @@ class Contact extends DataClass implements Insertable<Contact> {
? const Value.absent() ? const Value.absent()
: Value(userDiscoveryVersion), : Value(userDiscoveryVersion),
userDiscoveryExcluded: Value(userDiscoveryExcluded), userDiscoveryExcluded: Value(userDiscoveryExcluded),
askForFriendPromotions: askForFriendPromotions == null && nullToAbsent
? const Value.absent()
: Value(askForFriendPromotions),
userDiscoveryManualApproved: userDiscoveryManualApproved:
userDiscoveryManualApproved == null && nullToAbsent userDiscoveryManualApproved == null && nullToAbsent
? const Value.absent() ? const Value.absent()
@ -639,6 +675,9 @@ class Contact extends DataClass implements Insertable<Contact> {
userDiscoveryExcluded: serializer.fromJson<bool>( userDiscoveryExcluded: serializer.fromJson<bool>(
json['userDiscoveryExcluded'], json['userDiscoveryExcluded'],
), ),
askForFriendPromotions: serializer.fromJson<bool?>(
json['askForFriendPromotions'],
),
userDiscoveryManualApproved: serializer.fromJson<bool?>( userDiscoveryManualApproved: serializer.fromJson<bool?>(
json['userDiscoveryManualApproved'], json['userDiscoveryManualApproved'],
), ),
@ -669,6 +708,9 @@ class Contact extends DataClass implements Insertable<Contact> {
userDiscoveryVersion, userDiscoveryVersion,
), ),
'userDiscoveryExcluded': serializer.toJson<bool>(userDiscoveryExcluded), 'userDiscoveryExcluded': serializer.toJson<bool>(userDiscoveryExcluded),
'askForFriendPromotions': serializer.toJson<bool?>(
askForFriendPromotions,
),
'userDiscoveryManualApproved': serializer.toJson<bool?>( 'userDiscoveryManualApproved': serializer.toJson<bool?>(
userDiscoveryManualApproved, userDiscoveryManualApproved,
), ),
@ -693,6 +735,7 @@ class Contact extends DataClass implements Insertable<Contact> {
DateTime? createdAt, DateTime? createdAt,
Value<Uint8List?> userDiscoveryVersion = const Value.absent(), Value<Uint8List?> userDiscoveryVersion = const Value.absent(),
bool? userDiscoveryExcluded, bool? userDiscoveryExcluded,
Value<bool?> askForFriendPromotions = const Value.absent(),
Value<bool?> userDiscoveryManualApproved = const Value.absent(), Value<bool?> userDiscoveryManualApproved = const Value.absent(),
int? mediaSendCounter, int? mediaSendCounter,
int? mediaReceivedCounter, int? mediaReceivedCounter,
@ -716,6 +759,9 @@ class Contact extends DataClass implements Insertable<Contact> {
? userDiscoveryVersion.value ? userDiscoveryVersion.value
: this.userDiscoveryVersion, : this.userDiscoveryVersion,
userDiscoveryExcluded: userDiscoveryExcluded ?? this.userDiscoveryExcluded, userDiscoveryExcluded: userDiscoveryExcluded ?? this.userDiscoveryExcluded,
askForFriendPromotions: askForFriendPromotions.present
? askForFriendPromotions.value
: this.askForFriendPromotions,
userDiscoveryManualApproved: userDiscoveryManualApproved.present userDiscoveryManualApproved: userDiscoveryManualApproved.present
? userDiscoveryManualApproved.value ? userDiscoveryManualApproved.value
: this.userDiscoveryManualApproved, : this.userDiscoveryManualApproved,
@ -753,6 +799,9 @@ class Contact extends DataClass implements Insertable<Contact> {
userDiscoveryExcluded: data.userDiscoveryExcluded.present userDiscoveryExcluded: data.userDiscoveryExcluded.present
? data.userDiscoveryExcluded.value ? data.userDiscoveryExcluded.value
: this.userDiscoveryExcluded, : this.userDiscoveryExcluded,
askForFriendPromotions: data.askForFriendPromotions.present
? data.askForFriendPromotions.value
: this.askForFriendPromotions,
userDiscoveryManualApproved: data.userDiscoveryManualApproved.present userDiscoveryManualApproved: data.userDiscoveryManualApproved.present
? data.userDiscoveryManualApproved.value ? data.userDiscoveryManualApproved.value
: this.userDiscoveryManualApproved, : this.userDiscoveryManualApproved,
@ -783,6 +832,7 @@ class Contact extends DataClass implements Insertable<Contact> {
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('userDiscoveryVersion: $userDiscoveryVersion, ') ..write('userDiscoveryVersion: $userDiscoveryVersion, ')
..write('userDiscoveryExcluded: $userDiscoveryExcluded, ') ..write('userDiscoveryExcluded: $userDiscoveryExcluded, ')
..write('askForFriendPromotions: $askForFriendPromotions, ')
..write('userDiscoveryManualApproved: $userDiscoveryManualApproved, ') ..write('userDiscoveryManualApproved: $userDiscoveryManualApproved, ')
..write('mediaSendCounter: $mediaSendCounter, ') ..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter') ..write('mediaReceivedCounter: $mediaReceivedCounter')
@ -807,6 +857,7 @@ class Contact extends DataClass implements Insertable<Contact> {
createdAt, createdAt,
$driftBlobEquality.hash(userDiscoveryVersion), $driftBlobEquality.hash(userDiscoveryVersion),
userDiscoveryExcluded, userDiscoveryExcluded,
askForFriendPromotions,
userDiscoveryManualApproved, userDiscoveryManualApproved,
mediaSendCounter, mediaSendCounter,
mediaReceivedCounter, mediaReceivedCounter,
@ -836,6 +887,7 @@ class Contact extends DataClass implements Insertable<Contact> {
this.userDiscoveryVersion, this.userDiscoveryVersion,
) && ) &&
other.userDiscoveryExcluded == this.userDiscoveryExcluded && other.userDiscoveryExcluded == this.userDiscoveryExcluded &&
other.askForFriendPromotions == this.askForFriendPromotions &&
other.userDiscoveryManualApproved == other.userDiscoveryManualApproved ==
this.userDiscoveryManualApproved && this.userDiscoveryManualApproved &&
other.mediaSendCounter == this.mediaSendCounter && other.mediaSendCounter == this.mediaSendCounter &&
@ -858,6 +910,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
final Value<DateTime> createdAt; final Value<DateTime> createdAt;
final Value<Uint8List?> userDiscoveryVersion; final Value<Uint8List?> userDiscoveryVersion;
final Value<bool> userDiscoveryExcluded; final Value<bool> userDiscoveryExcluded;
final Value<bool?> askForFriendPromotions;
final Value<bool?> userDiscoveryManualApproved; final Value<bool?> userDiscoveryManualApproved;
final Value<int> mediaSendCounter; final Value<int> mediaSendCounter;
final Value<int> mediaReceivedCounter; final Value<int> mediaReceivedCounter;
@ -877,6 +930,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.userDiscoveryVersion = const Value.absent(), this.userDiscoveryVersion = const Value.absent(),
this.userDiscoveryExcluded = const Value.absent(), this.userDiscoveryExcluded = const Value.absent(),
this.askForFriendPromotions = const Value.absent(),
this.userDiscoveryManualApproved = const Value.absent(), this.userDiscoveryManualApproved = const Value.absent(),
this.mediaSendCounter = const Value.absent(), this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(), this.mediaReceivedCounter = const Value.absent(),
@ -897,6 +951,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
this.createdAt = const Value.absent(), this.createdAt = const Value.absent(),
this.userDiscoveryVersion = const Value.absent(), this.userDiscoveryVersion = const Value.absent(),
this.userDiscoveryExcluded = const Value.absent(), this.userDiscoveryExcluded = const Value.absent(),
this.askForFriendPromotions = const Value.absent(),
this.userDiscoveryManualApproved = const Value.absent(), this.userDiscoveryManualApproved = const Value.absent(),
this.mediaSendCounter = const Value.absent(), this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(), this.mediaReceivedCounter = const Value.absent(),
@ -917,6 +972,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Expression<DateTime>? createdAt, Expression<DateTime>? createdAt,
Expression<Uint8List>? userDiscoveryVersion, Expression<Uint8List>? userDiscoveryVersion,
Expression<bool>? userDiscoveryExcluded, Expression<bool>? userDiscoveryExcluded,
Expression<bool>? askForFriendPromotions,
Expression<bool>? userDiscoveryManualApproved, Expression<bool>? userDiscoveryManualApproved,
Expression<int>? mediaSendCounter, Expression<int>? mediaSendCounter,
Expression<int>? mediaReceivedCounter, Expression<int>? mediaReceivedCounter,
@ -941,6 +997,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
'user_discovery_version': userDiscoveryVersion, 'user_discovery_version': userDiscoveryVersion,
if (userDiscoveryExcluded != null) if (userDiscoveryExcluded != null)
'user_discovery_excluded': userDiscoveryExcluded, 'user_discovery_excluded': userDiscoveryExcluded,
if (askForFriendPromotions != null)
'ask_for_friend_promotions': askForFriendPromotions,
if (userDiscoveryManualApproved != null) if (userDiscoveryManualApproved != null)
'user_discovery_manual_approved': userDiscoveryManualApproved, 'user_discovery_manual_approved': userDiscoveryManualApproved,
if (mediaSendCounter != null) 'media_send_counter': mediaSendCounter, if (mediaSendCounter != null) 'media_send_counter': mediaSendCounter,
@ -965,6 +1023,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Value<DateTime>? createdAt, Value<DateTime>? createdAt,
Value<Uint8List?>? userDiscoveryVersion, Value<Uint8List?>? userDiscoveryVersion,
Value<bool>? userDiscoveryExcluded, Value<bool>? userDiscoveryExcluded,
Value<bool?>? askForFriendPromotions,
Value<bool?>? userDiscoveryManualApproved, Value<bool?>? userDiscoveryManualApproved,
Value<int>? mediaSendCounter, Value<int>? mediaSendCounter,
Value<int>? mediaReceivedCounter, Value<int>? mediaReceivedCounter,
@ -986,6 +1045,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
userDiscoveryVersion: userDiscoveryVersion ?? this.userDiscoveryVersion, userDiscoveryVersion: userDiscoveryVersion ?? this.userDiscoveryVersion,
userDiscoveryExcluded: userDiscoveryExcluded:
userDiscoveryExcluded ?? this.userDiscoveryExcluded, userDiscoveryExcluded ?? this.userDiscoveryExcluded,
askForFriendPromotions:
askForFriendPromotions ?? this.askForFriendPromotions,
userDiscoveryManualApproved: userDiscoveryManualApproved:
userDiscoveryManualApproved ?? this.userDiscoveryManualApproved, userDiscoveryManualApproved ?? this.userDiscoveryManualApproved,
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter, mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
@ -1047,6 +1108,11 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
userDiscoveryExcluded.value, userDiscoveryExcluded.value,
); );
} }
if (askForFriendPromotions.present) {
map['ask_for_friend_promotions'] = Variable<bool>(
askForFriendPromotions.value,
);
}
if (userDiscoveryManualApproved.present) { if (userDiscoveryManualApproved.present) {
map['user_discovery_manual_approved'] = Variable<bool>( map['user_discovery_manual_approved'] = Variable<bool>(
userDiscoveryManualApproved.value, userDiscoveryManualApproved.value,
@ -1079,6 +1145,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
..write('createdAt: $createdAt, ') ..write('createdAt: $createdAt, ')
..write('userDiscoveryVersion: $userDiscoveryVersion, ') ..write('userDiscoveryVersion: $userDiscoveryVersion, ')
..write('userDiscoveryExcluded: $userDiscoveryExcluded, ') ..write('userDiscoveryExcluded: $userDiscoveryExcluded, ')
..write('askForFriendPromotions: $askForFriendPromotions, ')
..write('userDiscoveryManualApproved: $userDiscoveryManualApproved, ') ..write('userDiscoveryManualApproved: $userDiscoveryManualApproved, ')
..write('mediaSendCounter: $mediaSendCounter, ') ..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter') ..write('mediaReceivedCounter: $mediaReceivedCounter')
@ -12796,6 +12863,7 @@ typedef $$ContactsTableCreateCompanionBuilder =
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<Uint8List?> userDiscoveryVersion, Value<Uint8List?> userDiscoveryVersion,
Value<bool> userDiscoveryExcluded, Value<bool> userDiscoveryExcluded,
Value<bool?> askForFriendPromotions,
Value<bool?> userDiscoveryManualApproved, Value<bool?> userDiscoveryManualApproved,
Value<int> mediaSendCounter, Value<int> mediaSendCounter,
Value<int> mediaReceivedCounter, Value<int> mediaReceivedCounter,
@ -12817,6 +12885,7 @@ typedef $$ContactsTableUpdateCompanionBuilder =
Value<DateTime> createdAt, Value<DateTime> createdAt,
Value<Uint8List?> userDiscoveryVersion, Value<Uint8List?> userDiscoveryVersion,
Value<bool> userDiscoveryExcluded, Value<bool> userDiscoveryExcluded,
Value<bool?> askForFriendPromotions,
Value<bool?> userDiscoveryManualApproved, Value<bool?> userDiscoveryManualApproved,
Value<int> mediaSendCounter, Value<int> mediaSendCounter,
Value<int> mediaReceivedCounter, Value<int> mediaReceivedCounter,
@ -13199,6 +13268,11 @@ class $$ContactsTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<bool> get askForFriendPromotions => $composableBuilder(
column: $table.askForFriendPromotions,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get userDiscoveryManualApproved => $composableBuilder( ColumnFilters<bool> get userDiscoveryManualApproved => $composableBuilder(
column: $table.userDiscoveryManualApproved, column: $table.userDiscoveryManualApproved,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
@ -13607,6 +13681,11 @@ class $$ContactsTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<bool> get askForFriendPromotions => $composableBuilder(
column: $table.askForFriendPromotions,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get userDiscoveryManualApproved => $composableBuilder( ColumnOrderings<bool> get userDiscoveryManualApproved => $composableBuilder(
column: $table.userDiscoveryManualApproved, column: $table.userDiscoveryManualApproved,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
@ -13691,6 +13770,11 @@ class $$ContactsTableAnnotationComposer
builder: (column) => column, builder: (column) => column,
); );
GeneratedColumn<bool> get askForFriendPromotions => $composableBuilder(
column: $table.askForFriendPromotions,
builder: (column) => column,
);
GeneratedColumn<bool> get userDiscoveryManualApproved => $composableBuilder( GeneratedColumn<bool> get userDiscoveryManualApproved => $composableBuilder(
column: $table.userDiscoveryManualApproved, column: $table.userDiscoveryManualApproved,
builder: (column) => column, builder: (column) => column,
@ -14076,6 +14160,7 @@ class $$ContactsTableTableManager
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<Uint8List?> userDiscoveryVersion = const Value.absent(), Value<Uint8List?> userDiscoveryVersion = const Value.absent(),
Value<bool> userDiscoveryExcluded = const Value.absent(), Value<bool> userDiscoveryExcluded = const Value.absent(),
Value<bool?> askForFriendPromotions = const Value.absent(),
Value<bool?> userDiscoveryManualApproved = const Value.absent(), Value<bool?> userDiscoveryManualApproved = const Value.absent(),
Value<int> mediaSendCounter = const Value.absent(), Value<int> mediaSendCounter = const Value.absent(),
Value<int> mediaReceivedCounter = const Value.absent(), Value<int> mediaReceivedCounter = const Value.absent(),
@ -14095,6 +14180,7 @@ class $$ContactsTableTableManager
createdAt: createdAt, createdAt: createdAt,
userDiscoveryVersion: userDiscoveryVersion, userDiscoveryVersion: userDiscoveryVersion,
userDiscoveryExcluded: userDiscoveryExcluded, userDiscoveryExcluded: userDiscoveryExcluded,
askForFriendPromotions: askForFriendPromotions,
userDiscoveryManualApproved: userDiscoveryManualApproved, userDiscoveryManualApproved: userDiscoveryManualApproved,
mediaSendCounter: mediaSendCounter, mediaSendCounter: mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter, mediaReceivedCounter: mediaReceivedCounter,
@ -14116,6 +14202,7 @@ class $$ContactsTableTableManager
Value<DateTime> createdAt = const Value.absent(), Value<DateTime> createdAt = const Value.absent(),
Value<Uint8List?> userDiscoveryVersion = const Value.absent(), Value<Uint8List?> userDiscoveryVersion = const Value.absent(),
Value<bool> userDiscoveryExcluded = const Value.absent(), Value<bool> userDiscoveryExcluded = const Value.absent(),
Value<bool?> askForFriendPromotions = const Value.absent(),
Value<bool?> userDiscoveryManualApproved = const Value.absent(), Value<bool?> userDiscoveryManualApproved = const Value.absent(),
Value<int> mediaSendCounter = const Value.absent(), Value<int> mediaSendCounter = const Value.absent(),
Value<int> mediaReceivedCounter = const Value.absent(), Value<int> mediaReceivedCounter = const Value.absent(),
@ -14135,6 +14222,7 @@ class $$ContactsTableTableManager
createdAt: createdAt, createdAt: createdAt,
userDiscoveryVersion: userDiscoveryVersion, userDiscoveryVersion: userDiscoveryVersion,
userDiscoveryExcluded: userDiscoveryExcluded, userDiscoveryExcluded: userDiscoveryExcluded,
askForFriendPromotions: askForFriendPromotions,
userDiscoveryManualApproved: userDiscoveryManualApproved, userDiscoveryManualApproved: userDiscoveryManualApproved,
mediaSendCounter: mediaSendCounter, mediaSendCounter: mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter, mediaReceivedCounter: mediaReceivedCounter,

View file

@ -9022,6 +9022,508 @@ i1.GeneratedColumn<int> _column_246(String aliasedName) =>
'NOT NULL DEFAULT 0 CHECK (was_asked_friends IN (0, 1))', 'NOT NULL DEFAULT 0 CHECK (was_asked_friends IN (0, 1))',
defaultValue: const i1.CustomExpression('0'), defaultValue: const i1.CustomExpression('0'),
); );
final class Schema18 extends i0.VersionedSchema {
Schema18({required super.database}) : super(version: 18);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
groups,
mediaFiles,
messages,
messageHistories,
reactions,
groupMembers,
receipts,
receivedReceipts,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
signalSignedPreKeyStores,
messageActions,
groupHistories,
keyVerifications,
verificationTokens,
userDiscoveryAnnouncedUsers,
userDiscoveryUserRelations,
userDiscoveryOtherPromotions,
userDiscoveryOwnPromotions,
userDiscoveryShares,
shortcuts,
shortcutMembers,
];
late final Shape53 contacts = Shape53(
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_212,
_column_247,
_column_213,
_column_214,
_column_215,
],
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 Shape51 mediaFiles = Shape51(
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_239,
_column_240,
_column_207,
_column_150,
_column_151,
_column_152,
_column_153,
_column_154,
_column_155,
_column_156,
_column_157,
_column_244,
_column_245,
_column_118,
_column_241,
],
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 Shape50 signalSignedPreKeyStores = Shape50(
source: i0.VersionedTable(
entityName: 'signal_signed_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(signed_pre_key_id)'],
columns: [_column_242, _column_243, _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: [],
columns: [_column_216, _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_217, _column_218, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape52 userDiscoveryAnnouncedUsers = Shape52(
source: i0.VersionedTable(
entityName: 'user_discovery_announced_users',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
columns: [
_column_219,
_column_220,
_column_221,
_column_222,
_column_223,
_column_224,
_column_246,
],
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_225, _column_226, _column_227],
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, public_id)'],
columns: [
_column_226,
_column_228,
_column_229,
_column_230,
_column_231,
_column_227,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape45 userDiscoveryOwnPromotions = Shape45(
source: i0.VersionedTable(
entityName: 'user_discovery_own_promotions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_232, _column_183, _column_233],
attachedDatabase: database,
),
alias: null,
);
late final Shape46 userDiscoveryShares = Shape46(
source: i0.VersionedTable(
entityName: 'user_discovery_shares',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_234, _column_235, _column_175],
attachedDatabase: database,
),
alias: null,
);
late final Shape47 shortcuts = Shape47(
source: i0.VersionedTable(
entityName: 'shortcuts',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_173, _column_236, _column_237],
attachedDatabase: database,
),
alias: null,
);
late final Shape48 shortcutMembers = Shape48(
source: i0.VersionedTable(
entityName: 'shortcut_members',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(shortcut_id, group_id)'],
columns: [_column_238, _column_158],
attachedDatabase: database,
),
alias: null,
);
}
class Shape53 extends i0.VersionedTable {
Shape53({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get userId =>
columnsByName['user_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get username =>
columnsByName['username']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get displayName =>
columnsByName['display_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get nickName =>
columnsByName['nick_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get avatarSvgCompressed =>
columnsByName['avatar_svg_compressed']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get senderProfileCounter =>
columnsByName['sender_profile_counter']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get accepted =>
columnsByName['accepted']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get deletedByUser =>
columnsByName['deleted_by_user']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get requested =>
columnsByName['requested']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get blocked =>
columnsByName['blocked']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get verified =>
columnsByName['verified']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get accountDeleted =>
columnsByName['account_deleted']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<i2.Uint8List> get userDiscoveryVersion =>
columnsByName['user_discovery_version']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get userDiscoveryExcluded =>
columnsByName['user_discovery_excluded']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get askForFriendPromotions =>
columnsByName['ask_for_friend_promotions']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get userDiscoveryManualApproved =>
columnsByName['user_discovery_manual_approved']!
as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get mediaSendCounter =>
columnsByName['media_send_counter']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get mediaReceivedCounter =>
columnsByName['media_received_counter']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_247(String aliasedName) =>
i1.GeneratedColumn<int>(
'ask_for_friend_promotions',
aliasedName,
true,
type: i1.DriftSqlType.int,
$customConstraints: 'NULL CHECK (ask_for_friend_promotions IN (0, 1))',
);
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,
@ -9039,6 +9541,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15, required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16, required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17, required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@ -9122,6 +9625,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from16To17(migrator, schema); await from16To17(migrator, schema);
return 17; return 17;
case 17:
final schema = Schema18(database: database);
final migrator = i1.Migrator(database, schema);
await from17To18(migrator, schema);
return 18;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@ -9145,6 +9653,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15, required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16, required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17, required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
}) => i0.VersionedSchema.stepByStepHelper( }) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
from1To2: from1To2, from1To2: from1To2,
@ -9163,5 +9672,6 @@ i1.OnUpgrade stepByStep({
from14To15: from14To15, from14To15: from14To15,
from15To16: from15To16, from15To16: from15To16,
from16To17: from16To17, from16To17: from16To17,
from17To18: from17To18,
), ),
); );

View file

@ -2768,6 +2768,12 @@ abstract class AppLocalizations {
/// **'{username} has scanned your QR code and is now verified.'** /// **'{username} has scanned your QR code and is now verified.'**
String secretQrTokenVerifiedSnackbar(Object username); String secretQrTokenVerifiedSnackbar(Object username);
/// No description provided for @askForFriendPromotionsPrompt.
///
/// In en, this message translates to:
/// **'Help {username} find familiar faces on twonly by sharing mutual friends.'**
String askForFriendPromotionsPrompt(Object username);
/// No description provided for @mutualGroupsTitle. /// No description provided for @mutualGroupsTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -1560,6 +1560,11 @@ class AppLocalizationsDe extends AppLocalizations {
return '$username hat deinen QR-Code gescannt und ist nun verifiziert.'; return '$username hat deinen QR-Code gescannt und ist nun verifiziert.';
} }
@override
String askForFriendPromotionsPrompt(Object username) {
return 'Hilf $username, bekannte Gesichter auf twonly zu finden, indem du gemeinsame Freunde teilst.';
}
@override @override
String mutualGroupsTitle(num count) { String mutualGroupsTitle(num count) {
String _temp0 = intl.Intl.pluralLogic( String _temp0 = intl.Intl.pluralLogic(

View file

@ -1547,6 +1547,11 @@ class AppLocalizationsEn extends AppLocalizations {
return '$username has scanned your QR code and is now verified.'; return '$username has scanned your QR code and is now verified.';
} }
@override
String askForFriendPromotionsPrompt(Object username) {
return 'Help $username find familiar faces on twonly by sharing mutual friends.';
}
@override @override
String mutualGroupsTitle(num count) { String mutualGroupsTitle(num count) {
String _temp0 = intl.Intl.pluralLogic( String _temp0 = intl.Intl.pluralLogic(

@ -1 +1 @@
Subproject commit b6a106eebce0f5cbfe4272e47721929f32ed325c Subproject commit b508873f4a46cda607d03ee91c66a7907e22bf0a

View file

@ -128,6 +128,9 @@ class UserData {
// -- Custom DATA -- // -- Custom DATA --
@JsonKey(defaultValue: true)
bool askForFriendPromotions = true;
@JsonKey(defaultValue: 100_000) @JsonKey(defaultValue: 100_000)
int currentPreKeyIndexStart = 100_000; int currentPreKeyIndexStart = 100_000;

View file

@ -85,6 +85,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
json['userDiscoverySharePromotion'] as bool? ?? true json['userDiscoverySharePromotion'] as bool? ?? true
..userDiscoveryInitializationError = ..userDiscoveryInitializationError =
json['userDiscoveryInitializationError'] as bool? ?? false json['userDiscoveryInitializationError'] as bool? ?? false
..askForFriendPromotions = json['askForFriendPromotions'] as bool? ?? true
..currentPreKeyIndexStart = ..currentPreKeyIndexStart =
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000 (json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
..currentSignedPreKeyIndexStart = ..currentSignedPreKeyIndexStart =
@ -161,6 +162,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
instance.userDiscoveryRequiresManualApproval, instance.userDiscoveryRequiresManualApproval,
'userDiscoverySharePromotion': instance.userDiscoverySharePromotion, 'userDiscoverySharePromotion': instance.userDiscoverySharePromotion,
'userDiscoveryInitializationError': instance.userDiscoveryInitializationError, 'userDiscoveryInitializationError': instance.userDiscoveryInitializationError,
'askForFriendPromotions': instance.askForFriendPromotions,
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart, 'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart, 'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
'lastChangeLogHash': instance.lastChangeLogHash, 'lastChangeLogHash': instance.lastChangeLogHash,

View file

@ -1852,6 +1852,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
EncryptedContent_UserDiscoveryRequest? userDiscoveryRequest, EncryptedContent_UserDiscoveryRequest? userDiscoveryRequest,
EncryptedContent_UserDiscoveryUpdate? userDiscoveryUpdate, EncryptedContent_UserDiscoveryUpdate? userDiscoveryUpdate,
EncryptedContent_KeyVerificationProof? keyVerificationProof, EncryptedContent_KeyVerificationProof? keyVerificationProof,
$core.bool? askForFriendPromotions,
}) { }) {
final result = create(); final result = create();
if (groupId != null) result.groupId = groupId; if (groupId != null) result.groupId = groupId;
@ -1884,6 +1885,8 @@ class EncryptedContent extends $pb.GeneratedMessage {
result.userDiscoveryUpdate = userDiscoveryUpdate; result.userDiscoveryUpdate = userDiscoveryUpdate;
if (keyVerificationProof != null) if (keyVerificationProof != null)
result.keyVerificationProof = keyVerificationProof; result.keyVerificationProof = keyVerificationProof;
if (askForFriendPromotions != null)
result.askForFriendPromotions = askForFriendPromotions;
return result; return result;
} }
@ -1955,6 +1958,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
..aOM<EncryptedContent_KeyVerificationProof>( ..aOM<EncryptedContent_KeyVerificationProof>(
24, _omitFieldNames ? '' : 'keyVerificationProof', 24, _omitFieldNames ? '' : 'keyVerificationProof',
subBuilder: EncryptedContent_KeyVerificationProof.create) subBuilder: EncryptedContent_KeyVerificationProof.create)
..aOB(25, _omitFieldNames ? '' : 'askForFriendPromotions')
..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.')
@ -2238,6 +2242,15 @@ class EncryptedContent extends $pb.GeneratedMessage {
@$pb.TagNumber(24) @$pb.TagNumber(24)
EncryptedContent_KeyVerificationProof ensureKeyVerificationProof() => EncryptedContent_KeyVerificationProof ensureKeyVerificationProof() =>
$_ensure(22); $_ensure(22);
@$pb.TagNumber(25)
$core.bool get askForFriendPromotions => $_getBF(23);
@$pb.TagNumber(25)
set askForFriendPromotions($core.bool value) => $_setBool(23, value);
@$pb.TagNumber(25)
$core.bool hasAskForFriendPromotions() => $_has(23);
@$pb.TagNumber(25)
void clearAskForFriendPromotions() => $_clearField(25);
} }
const $core.bool _omitFieldNames = const $core.bool _omitFieldNames =

View file

@ -186,13 +186,22 @@ const EncryptedContent$json = {
'10': 'senderUserDiscoveryVersion', '10': 'senderUserDiscoveryVersion',
'17': true '17': true
}, },
{
'1': 'ask_for_friend_promotions',
'3': 25,
'4': 1,
'5': 8,
'9': 4,
'10': 'askForFriendPromotions',
'17': true
},
{ {
'1': 'message_update', '1': 'message_update',
'3': 5, '3': 5,
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.MessageUpdate', '6': '.EncryptedContent.MessageUpdate',
'9': 4, '9': 5,
'10': 'messageUpdate', '10': 'messageUpdate',
'17': true '17': true
}, },
@ -202,7 +211,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.Media', '6': '.EncryptedContent.Media',
'9': 5, '9': 6,
'10': 'media', '10': 'media',
'17': true '17': true
}, },
@ -212,7 +221,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.MediaUpdate', '6': '.EncryptedContent.MediaUpdate',
'9': 6, '9': 7,
'10': 'mediaUpdate', '10': 'mediaUpdate',
'17': true '17': true
}, },
@ -222,7 +231,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ContactUpdate', '6': '.EncryptedContent.ContactUpdate',
'9': 7, '9': 8,
'10': 'contactUpdate', '10': 'contactUpdate',
'17': true '17': true
}, },
@ -232,7 +241,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ContactRequest', '6': '.EncryptedContent.ContactRequest',
'9': 8, '9': 9,
'10': 'contactRequest', '10': 'contactRequest',
'17': true '17': true
}, },
@ -242,7 +251,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.FlameSync', '6': '.EncryptedContent.FlameSync',
'9': 9, '9': 10,
'10': 'flameSync', '10': 'flameSync',
'17': true '17': true
}, },
@ -252,7 +261,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.PushKeys', '6': '.EncryptedContent.PushKeys',
'9': 10, '9': 11,
'10': 'pushKeys', '10': 'pushKeys',
'17': true '17': true
}, },
@ -262,7 +271,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.Reaction', '6': '.EncryptedContent.Reaction',
'9': 11, '9': 12,
'10': 'reaction', '10': 'reaction',
'17': true '17': true
}, },
@ -272,7 +281,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.TextMessage', '6': '.EncryptedContent.TextMessage',
'9': 12, '9': 13,
'10': 'textMessage', '10': 'textMessage',
'17': true '17': true
}, },
@ -282,7 +291,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.GroupCreate', '6': '.EncryptedContent.GroupCreate',
'9': 13, '9': 14,
'10': 'groupCreate', '10': 'groupCreate',
'17': true '17': true
}, },
@ -292,7 +301,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.GroupJoin', '6': '.EncryptedContent.GroupJoin',
'9': 14, '9': 15,
'10': 'groupJoin', '10': 'groupJoin',
'17': true '17': true
}, },
@ -302,7 +311,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.GroupUpdate', '6': '.EncryptedContent.GroupUpdate',
'9': 15, '9': 16,
'10': 'groupUpdate', '10': 'groupUpdate',
'17': true '17': true
}, },
@ -312,7 +321,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ResendGroupPublicKey', '6': '.EncryptedContent.ResendGroupPublicKey',
'9': 16, '9': 17,
'10': 'resendGroupPublicKey', '10': 'resendGroupPublicKey',
'17': true '17': true
}, },
@ -322,7 +331,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ErrorMessages', '6': '.EncryptedContent.ErrorMessages',
'9': 17, '9': 18,
'10': 'errorMessages', '10': 'errorMessages',
'17': true '17': true
}, },
@ -332,7 +341,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.AdditionalDataMessage', '6': '.EncryptedContent.AdditionalDataMessage',
'9': 18, '9': 19,
'10': 'additionalDataMessage', '10': 'additionalDataMessage',
'17': true '17': true
}, },
@ -342,7 +351,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.TypingIndicator', '6': '.EncryptedContent.TypingIndicator',
'9': 19, '9': 20,
'10': 'typingIndicator', '10': 'typingIndicator',
'17': true '17': true
}, },
@ -352,7 +361,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.UserDiscoveryRequest', '6': '.EncryptedContent.UserDiscoveryRequest',
'9': 20, '9': 21,
'10': 'userDiscoveryRequest', '10': 'userDiscoveryRequest',
'17': true '17': true
}, },
@ -362,7 +371,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.UserDiscoveryUpdate', '6': '.EncryptedContent.UserDiscoveryUpdate',
'9': 21, '9': 22,
'10': 'userDiscoveryUpdate', '10': 'userDiscoveryUpdate',
'17': true '17': true
}, },
@ -372,7 +381,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.KeyVerificationProof', '6': '.EncryptedContent.KeyVerificationProof',
'9': 22, '9': 23,
'10': 'keyVerificationProof', '10': 'keyVerificationProof',
'17': true '17': true
}, },
@ -403,6 +412,7 @@ const EncryptedContent$json = {
{'1': '_is_direct_chat'}, {'1': '_is_direct_chat'},
{'1': '_sender_profile_counter'}, {'1': '_sender_profile_counter'},
{'1': '_sender_user_discovery_version'}, {'1': '_sender_user_discovery_version'},
{'1': '_ask_for_friend_promotions'},
{'1': '_message_update'}, {'1': '_message_update'},
{'1': '_media'}, {'1': '_media'},
{'1': '_media_update'}, {'1': '_media_update'},
@ -938,103 +948,105 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'NfZGlyZWN0X2NoYXQYAyABKAhIAVIMaXNEaXJlY3RDaGF0iAEBEjkKFnNlbmRlcl9wcm9maWxl' 'NfZGlyZWN0X2NoYXQYAyABKAhIAVIMaXNEaXJlY3RDaGF0iAEBEjkKFnNlbmRlcl9wcm9maWxl'
'X2NvdW50ZXIYBCABKANIAlIUc2VuZGVyUHJvZmlsZUNvdW50ZXKIAQESRgodc2VuZGVyX3VzZX' 'X2NvdW50ZXIYBCABKANIAlIUc2VuZGVyUHJvZmlsZUNvdW50ZXKIAQESRgodc2VuZGVyX3VzZX'
'JfZGlzY292ZXJ5X3ZlcnNpb24YFSABKAxIA1Iac2VuZGVyVXNlckRpc2NvdmVyeVZlcnNpb26I' 'JfZGlzY292ZXJ5X3ZlcnNpb24YFSABKAxIA1Iac2VuZGVyVXNlckRpc2NvdmVyeVZlcnNpb26I'
'AQESSwoObWVzc2FnZV91cGRhdGUYBSABKAsyHy5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcG' 'AQESPgoZYXNrX2Zvcl9mcmllbmRfcHJvbW90aW9ucxgZIAEoCEgEUhZhc2tGb3JGcmllbmRQcm'
'RhdGVIBFINbWVzc2FnZVVwZGF0ZYgBARIyCgVtZWRpYRgGIAEoCzIXLkVuY3J5cHRlZENvbnRl' '9tb3Rpb25ziAEBEksKDm1lc3NhZ2VfdXBkYXRlGAUgASgLMh8uRW5jcnlwdGVkQ29udGVudC5N'
'bnQuTWVkaWFIBVIFbWVkaWGIAQESRQoMbWVkaWFfdXBkYXRlGAcgASgLMh0uRW5jcnlwdGVkQ2' 'ZXNzYWdlVXBkYXRlSAVSDW1lc3NhZ2VVcGRhdGWIAQESMgoFbWVkaWEYBiABKAsyFy5FbmNyeX'
'9udGVudC5NZWRpYVVwZGF0ZUgGUgttZWRpYVVwZGF0ZYgBARJLCg5jb250YWN0X3VwZGF0ZRgI' 'B0ZWRDb250ZW50Lk1lZGlhSAZSBW1lZGlhiAEBEkUKDG1lZGlhX3VwZGF0ZRgHIAEoCzIdLkVu'
'IAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZUgHUg1jb250YWN0VXBkYXRliA' 'Y3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGVIB1ILbWVkaWFVcGRhdGWIAQESSwoOY29udGFjdF'
'EBEk4KD2NvbnRhY3RfcmVxdWVzdBgJIAEoCzIgLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJl' '91cGRhdGUYCCABKAsyHy5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGVICFINY29udGFj'
'cXVlc3RICFIOY29udGFjdFJlcXVlc3SIAQESPwoKZmxhbWVfc3luYxgKIAEoCzIbLkVuY3J5cH' 'dFVwZGF0ZYgBARJOCg9jb250YWN0X3JlcXVlc3QYCSABKAsyIC5FbmNyeXB0ZWRDb250ZW50Lk'
'RlZENvbnRlbnQuRmxhbWVTeW5jSAlSCWZsYW1lU3luY4gBARI8CglwdXNoX2tleXMYCyABKAsy' 'NvbnRhY3RSZXF1ZXN0SAlSDmNvbnRhY3RSZXF1ZXN0iAEBEj8KCmZsYW1lX3N5bmMYCiABKAsy'
'Gi5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzSApSCHB1c2hLZXlziAEBEjsKCHJlYWN0aW9uGA' 'Gy5FbmNyeXB0ZWRDb250ZW50LkZsYW1lU3luY0gKUglmbGFtZVN5bmOIAQESPAoJcHVzaF9rZX'
'wgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgLUghyZWFjdGlvbogBARJFCgx0ZXh0' 'lzGAsgASgLMhouRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5c0gLUghwdXNoS2V5c4gBARI7Cghy'
'X21lc3NhZ2UYDSABKAsyHS5FbmNyeXB0ZWRDb250ZW50LlRleHRNZXNzYWdlSAxSC3RleHRNZX' 'ZWFjdGlvbhgMIAEoCzIaLkVuY3J5cHRlZENvbnRlbnQuUmVhY3Rpb25IDFIIcmVhY3Rpb26IAQ'
'NzYWdliAEBEkUKDGdyb3VwX2NyZWF0ZRgOIAEoCzIdLkVuY3J5cHRlZENvbnRlbnQuR3JvdXBD' 'ESRQoMdGV4dF9tZXNzYWdlGA0gASgLMh0uRW5jcnlwdGVkQ29udGVudC5UZXh0TWVzc2FnZUgN'
'cmVhdGVIDVILZ3JvdXBDcmVhdGWIAQESPwoKZ3JvdXBfam9pbhgPIAEoCzIbLkVuY3J5cHRlZE' 'Ugt0ZXh0TWVzc2FnZYgBARJFCgxncm91cF9jcmVhdGUYDiABKAsyHS5FbmNyeXB0ZWRDb250ZW'
'NvbnRlbnQuR3JvdXBKb2luSA5SCWdyb3VwSm9pbogBARJFCgxncm91cF91cGRhdGUYECABKAsy' '50Lkdyb3VwQ3JlYXRlSA5SC2dyb3VwQ3JlYXRliAEBEj8KCmdyb3VwX2pvaW4YDyABKAsyGy5F'
'HS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSA9SC2dyb3VwVXBkYXRliAEBEmIKF3Jlc2' 'bmNyeXB0ZWRDb250ZW50Lkdyb3VwSm9pbkgPUglncm91cEpvaW6IAQESRQoMZ3JvdXBfdXBkYX'
'VuZF9ncm91cF9wdWJsaWNfa2V5GBEgASgLMiYuRW5jcnlwdGVkQ29udGVudC5SZXNlbmRHcm91' 'RlGBAgASgLMh0uRW5jcnlwdGVkQ29udGVudC5Hcm91cFVwZGF0ZUgQUgtncm91cFVwZGF0ZYgB'
'cFB1YmxpY0tleUgQUhRyZXNlbmRHcm91cFB1YmxpY0tleYgBARJLCg5lcnJvcl9tZXNzYWdlcx' 'ARJiChdyZXNlbmRfZ3JvdXBfcHVibGljX2tleRgRIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuUm'
'gSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlc0gRUg1lcnJvck1lc3NhZ2Vz' 'VzZW5kR3JvdXBQdWJsaWNLZXlIEVIUcmVzZW5kR3JvdXBQdWJsaWNLZXmIAQESSwoOZXJyb3Jf'
'iAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgASgLMicuRW5jcnlwdGVkQ29udGVudC' 'bWVzc2FnZXMYEiABKAsyHy5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZXNIElINZXJyb3'
'5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIElIVYWRkaXRpb25hbERhdGFNZXNzYWdliAEBElEKEHR5' 'JNZXNzYWdlc4gBARJkChdhZGRpdGlvbmFsX2RhdGFfbWVzc2FnZRgTIAEoCzInLkVuY3J5cHRl'
'cGluZ19pbmRpY2F0b3IYFCABKAsyIS5FbmNyeXB0ZWRDb250ZW50LlR5cGluZ0luZGljYXRvck' 'ZENvbnRlbnQuQWRkaXRpb25hbERhdGFNZXNzYWdlSBNSFWFkZGl0aW9uYWxEYXRhTWVzc2FnZY'
'gTUg90eXBpbmdJbmRpY2F0b3KIAQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEoCzIm' 'gBARJRChB0eXBpbmdfaW5kaWNhdG9yGBQgASgLMiEuRW5jcnlwdGVkQ29udGVudC5UeXBpbmdJ'
'LkVuY3J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVJlcXVlc3RIFFIUdXNlckRpc2NvdmVyeV' 'bmRpY2F0b3JIFFIPdHlwaW5nSW5kaWNhdG9yiAEBEmEKFnVzZXJfZGlzY292ZXJ5X3JlcXVlc3'
'JlcXVlc3SIAQESXgoVdXNlcl9kaXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVkQ29u' 'QYFiABKAsyJi5FbmNyeXB0ZWRDb250ZW50LlVzZXJEaXNjb3ZlcnlSZXF1ZXN0SBVSFHVzZXJE'
'dGVudC5Vc2VyRGlzY292ZXJ5VXBkYXRlSBVSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQESYQoWa2' 'aXNjb3ZlcnlSZXF1ZXN0iAEBEl4KFXVzZXJfZGlzY292ZXJ5X3VwZGF0ZRgXIAEoCzIlLkVuY3'
'V5X3ZlcmlmaWNhdGlvbl9wcm9vZhgYIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuS2V5VmVyaWZp' 'J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVVwZGF0ZUgWUhN1c2VyRGlzY292ZXJ5VXBkYXRl'
'Y2F0aW9uUHJvb2ZIFlIUa2V5VmVyaWZpY2F0aW9uUHJvb2aIAQEa8AEKDUVycm9yTWVzc2FnZX' 'iAEBEmEKFmtleV92ZXJpZmljYXRpb25fcHJvb2YYGCABKAsyJi5FbmNyeXB0ZWRDb250ZW50Lk'
'MSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeXBlUgR0' 'tleVZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBGvABCg1FcnJv'
'eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCJ3CgRUeX' 'ck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZX'
'BlEjwKOEVSUk9SX1BST0NFU1NJTkdfTUVTU0FHRV9DUkVBVEVEX0FDQ09VTlRfUkVRVUVTVF9J' 'MuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRfaWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0'
'TlNURUFEEAASGAoUVU5LTk9XTl9NRVNTQUdFX1RZUEUQAhIXChNTRVNTSU9OX09VVF9PRl9TWU' 'SWQidwoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1'
'5DEAMaVAoLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91' 'JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVTU0FHRV9UWVBFEAISFwoTU0VTU0lPTl9P'
'cF9wdWJsaWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRo1CglHcm91cEpvaW4SKAoQZ3JvdX' 'VVRfT0ZfU1lOQxADGlQKC0dyb3VwQ3JlYXRlEhsKCXN0YXRlX2tleRgDIAEoDFIIc3RhdGVLZX'
'BfcHVibGljX2tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNL' 'kSKAoQZ3JvdXBfcHVibGljX2tleRgEIAEoDFIOZ3JvdXBQdWJsaWNLZXkaNQoJR3JvdXBKb2lu'
'ZXkayAIKC0dyb3VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdG' 'EigKEGdyb3VwX3B1YmxpY19rZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3'
'lvblR5cGUSMwoTYWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJ' 'VwUHVibGljS2V5GsgCCgtHcm91cFVwZGF0ZRIqChFncm91cF9hY3Rpb25fdHlwZRgBIAEoCVIP'
'ZIgBARIpCg5uZXdfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2' 'Z3JvdXBBY3Rpb25UeXBlEjMKE2FmZmVjdGVkX2NvbnRhY3RfaWQYAiABKANIAFIRYWZmZWN0ZW'
'RlbGV0ZV9tZXNzYWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVz' 'RDb250YWN0SWSIAQESKQoObmV3X2dyb3VwX25hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEB'
'c2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbm' 'ElcKJm5ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGAQgASgDSAJSIm5ld0'
'V3X2dyb3VwX25hbWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRz' 'RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHOIAQFCFgoUX2FmZmVjdGVkX2NvbnRhY3Rf'
'Gq8BCgtUZXh0TWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2' 'aWRCEQoPX25ld19ncm91cF9uYW1lQikKJ19uZXdfZGVsZXRlX21lc3NhZ2VzX2FmdGVyX21pbG'
'FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXAS' 'xpc2Vjb25kcxqvAQoLVGV4dE1lc3NhZ2USKgoRc2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3Nl'
'LQoQcXVvdGVfbWVzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUITChFfcXVvdG' 'bmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdG'
'VfbWVzc2FnZV9pZBrOAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEioKEXNlbmRlcl9tZXNzYWdl' 'ltZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2VfaWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFC'
'X2lkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1wGAIgASgDUgl0aW1lc3RhbX' 'EwoRX3F1b3RlX21lc3NhZ2VfaWQazgEKFUFkZGl0aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZX'
'ASEgoEdHlwZRgDIAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgEIAEoDEgA' 'JfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJ'
'UhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQFCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGm' 'dGltZXN0YW1wEhIKBHR5cGUYAyABKAlSBHR5cGUSOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdG'
'QKCFJlYWN0aW9uEioKEXRhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQS' 'EYBCABKAxIAFIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3Nh'
'FAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGr4CCg1NZXNzYW' 'Z2VfZGF0YRpkCghSZWFjdGlvbhIqChF0YXJnZXRfbWVzc2FnZV9pZBgBIAEoCVIPdGFyZ2V0TW'
'dlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUu' 'Vzc2FnZUlkEhQKBWVtb2ppGAIgASgJUgVlbW9qaRIWCgZyZW1vdmUYAyABKAhSBnJlbW92ZRq+'
'VHlwZVIEdHlwZRIvChFzZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSW' 'AgoNTWVzc2FnZVVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5NZXNzYW'
'SIAQESPQobbXVsdGlwbGVfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhhtdWx0aXBsZVRhcmdl' 'dlVXBkYXRlLlR5cGVSBHR5cGUSLwoRc2VuZGVyX21lc3NhZ2VfaWQYAiABKAlIAFIPc2VuZGVy'
'dE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1' 'TWVzc2FnZUlkiAEBEj0KG211bHRpcGxlX3RhcmdldF9tZXNzYWdlX2lkcxgDIAMoCVIYbXVsdG'
'IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVE' 'lwbGVUYXJnZXRNZXNzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3Rh'
'EAJCFAoSX3NlbmRlcl9tZXNzYWdlX2lkQgcKBV90ZXh0GoUGCgVNZWRpYRIqChFzZW5kZXJfbW' 'bXAYBSABKANSCXRpbWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEg'
'Vzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0' 'oKBk9QRU5FRBACQhQKEl9zZW5kZXJfbWVzc2FnZV9pZEIHCgVfdGV4dBqFBgoFTWVkaWESKgoR'
'ZWRDb250ZW50Lk1lZGlhLlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY2' 'c2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGAIgASgOMh'
'9uZHMYAyABKANIAFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNwoXcmVxdWlyZXNf' 'wuRW5jcnlwdGVkQ29udGVudC5NZWRpYS5UeXBlUgR0eXBlEkYKHWRpc3BsYXlfbGltaXRfaW5f'
'YXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZXN0YW' 'bWlsbGlzZWNvbmRzGAMgASgDSABSGmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEjcKF3'
'1wGAUgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEoCUgBUg5xdW90ZU1l' 'JlcXVpcmVzX2F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uEhwK'
'c3NhZ2VJZIgBARIqCg5kb3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEi' 'CXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2VfaWQYBiABKAlIAV'
'oKDmVuY3J5cHRpb25fa2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQESKgoOZW5jcnlwdGlv' 'IOcXVvdGVNZXNzYWdlSWSIAQESKgoOZG93bmxvYWRfdG9rZW4YByABKAxIAlINZG93bmxvYWRU'
'bl9tYWMYCSABKAxIBFINZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW9uX25vbmNlGAogAS' 'b2tlbogBARIqCg5lbmNyeXB0aW9uX2tleRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEioKDm'
'gMSAVSD2VuY3J5cHRpb25Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgLIAEo' 'VuY3J5cHRpb25fbWFjGAkgASgMSARSDWVuY3J5cHRpb25NYWOIAQESLgoQZW5jcnlwdGlvbl9u'
'DEgGUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQEiPgoEVHlwZRIMCghSRVVQTE9BRBAAEgkKBU' 'b25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNzYWdlX2'
'lNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQiAKHl9kaXNwbGF5X2xpbWl0' 'RhdGEYCyABKAxIBlIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUkVVUExP'
'X2luX21pbGxpc2Vjb25kc0ITChFfcXVvdGVfbWVzc2FnZV9pZEIRCg9fZG93bmxvYWRfdG9rZW' 'QUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIgCh5fZGlzcG'
'5CEQoPX2VuY3J5cHRpb25fa2V5QhEKD19lbmNyeXB0aW9uX21hY0ITChFfZW5jcnlwdGlvbl9u' 'xheV9saW1pdF9pbl9taWxsaXNlY29uZHNCEwoRX3F1b3RlX21lc3NhZ2VfaWRCEQoPX2Rvd25s'
'b25jZUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEaqQEKC01lZGlhVXBkYXRlEjYKBHR5cG' 'b2FkX3Rva2VuQhEKD19lbmNyeXB0aW9uX2tleUIRCg9fZW5jcnlwdGlvbl9tYWNCEwoRX2VuY3'
'UYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKgoRdGFy' 'J5cHRpb25fbm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGqkBCgtNZWRpYVVwZGF0'
'Z2V0X21lc3NhZ2VfaWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTk' 'ZRI2CgR0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eX'
'VEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0' 'BlEioKEXRhcmdldF9tZXNzYWdlX2lkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIM'
'EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBH' 'CghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YW'
'R5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIapAIKDUNv' 'N0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVz'
'bnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZG' 'dC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVB'
'F0ZS5UeXBlUgR0eXBlEjcKFWF2YXRhcl9zdmdfY29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJT' 'ACGqQCCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNv'
'dmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiYKDGRpc3' 'bnRhY3RVcGRhdGUuVHlwZVIEdHlwZRI3ChVhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAF'
'BsYXlfbmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIK' 'ITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgB'
'CgZVUERBVEUQAUIYChZfYXZhdGFyX3N2Z19jb21wcmVzc2VkQgsKCV91c2VybmFtZUIPCg1fZG' 'ARImCgxkaXNwbGF5X25hbWUYBCABKAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRV'
'lzcGxheV9uYW1lGtkBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVu' 'FVRVNUEAASCgoGVVBEQVRFEAFCGAoWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5h'
'dC5QdXNoS2V5cy5UeXBlUgR0eXBlEhoKBmtleV9pZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZX' 'bWVCDwoNX2Rpc3BsYXlfbmFtZRrZAQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cH'
'kYAyABKAxIAVIDa2V5iAEBEiIKCmNyZWF0ZWRfYXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8K' 'RlZENvbnRlbnQuUHVzaEtleXMuVHlwZVIEdHlwZRIaCgZrZXlfaWQYAiABKANIAFIFa2V5SWSI'
'BFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQgkKB19rZXlfaWRCBgoEX2tleUINCgtfY3' 'AQESFQoDa2V5GAMgASgMSAFSA2tleYgBARIiCgpjcmVhdGVkX2F0GAQgASgDSAJSCWNyZWF0ZW'
'JlYXRlZF9hdBqvAQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDGZsYW1lQ291' 'RBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIJCgdfa2V5X2lkQgYKBF9r'
'bnRlchI5ChlsYXN0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudG' 'ZXlCDQoLX2NyZWF0ZWRfYXQarwEKCUZsYW1lU3luYxIjCg1mbGFtZV9jb3VudGVyGAEgASgDUg'
'VyQ2hhbmdlEh8KC2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcmNlX3VwZGF0' 'xmbGFtZUNvdW50ZXISOQoZbGFzdF9mbGFtZV9jb3VudGVyX2NoYW5nZRgCIAEoA1IWbGFzdEZs'
'ZRgEIAEoCFILZm9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5cGluZxgBIA' 'YW1lQ291bnRlckNoYW5nZRIfCgtiZXN0X2ZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZBIhCgxmb3'
'EoCFIIaXNUeXBpbmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFFVzZXJEaXNj' 'JjZV91cGRhdGUYBCABKAhSC2ZvcmNlVXBkYXRlGk0KD1R5cGluZ0luZGljYXRvchIbCglpc190'
'b3ZlcnlSZXF1ZXN0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZlcnNpb24aMQ' 'eXBpbmcYASABKAhSCGlzVHlwaW5nEh0KCmNyZWF0ZWRfYXQYAiABKANSCWNyZWF0ZWRBdBo/Ch'
'oTVXNlckRpc2NvdmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZXMaPQoUS2V5' 'RVc2VyRGlzY292ZXJ5UmVxdWVzdBInCg9jdXJyZW50X3ZlcnNpb24YASABKAxSDmN1cnJlbnRW'
'VmVyaWZpY2F0aW9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1bGF0ZWRNYW' 'ZXJzaW9uGjEKE1VzZXJEaXNjb3ZlcnlVcGRhdGUSGgoIbWVzc2FnZXMYASADKAxSCG1lc3NhZ2'
'NCCwoJX2dyb3VwX2lkQhEKD19pc19kaXJlY3RfY2hhdEIZChdfc2VuZGVyX3Byb2ZpbGVfY291' 'VzGj0KFEtleVZlcmlmaWNhdGlvblByb29mEiUKDmNhbGN1bGF0ZWRfbWFjGAEgASgMUg1jYWxj'
'bnRlckIgCh5fc2VuZGVyX3VzZXJfZGlzY292ZXJ5X3ZlcnNpb25CEQoPX21lc3NhZ2VfdXBkYX' 'dWxhdGVkTWFjQgsKCV9ncm91cF9pZEIRCg9faXNfZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm'
'RlQggKBl9tZWRpYUIPCg1fbWVkaWFfdXBkYXRlQhEKD19jb250YWN0X3VwZGF0ZUISChBfY29u' '9maWxlX2NvdW50ZXJCIAoeX3NlbmRlcl91c2VyX2Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tf'
'dGFjdF9yZXF1ZXN0Qg0KC19mbGFtZV9zeW5jQgwKCl9wdXNoX2tleXNCCwoJX3JlYWN0aW9uQg' 'Zm9yX2ZyaWVuZF9wcm9tb3Rpb25zQhEKD19tZXNzYWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX2'
'8KDV90ZXh0X21lc3NhZ2VCDwoNX2dyb3VwX2NyZWF0ZUINCgtfZ3JvdXBfam9pbkIPCg1fZ3Jv' '1lZGlhX3VwZGF0ZUIRCg9fY29udGFjdF91cGRhdGVCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtf'
'dXBfdXBkYXRlQhoKGF9yZXNlbmRfZ3JvdXBfcHVibGljX2tleUIRCg9fZXJyb3JfbWVzc2FnZX' 'ZmxhbWVfc3luY0IMCgpfcHVzaF9rZXlzQgsKCV9yZWFjdGlvbkIPCg1fdGV4dF9tZXNzYWdlQg'
'NCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlQhMKEV90eXBpbmdfaW5kaWNhdG9yQhkKF191' '8KDV9ncm91cF9jcmVhdGVCDQoLX2dyb3VwX2pvaW5CDwoNX2dyb3VwX3VwZGF0ZUIaChhfcmVz'
'c2VyX2Rpc2NvdmVyeV9yZXF1ZXN0QhgKFl91c2VyX2Rpc2NvdmVyeV91cGRhdGVCGQoXX2tleV' 'ZW5kX2dyb3VwX3B1YmxpY19rZXlCEQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2'
'92ZXJpZmljYXRpb25fcHJvb2Y='); 'RhdGFfbWVzc2FnZUITChFfdHlwaW5nX2luZGljYXRvckIZChdfdXNlcl9kaXNjb3ZlcnlfcmVx'
'dWVzdEIYChZfdXNlcl9kaXNjb3ZlcnlfdXBkYXRlQhkKF19rZXlfdmVyaWZpY2F0aW9uX3Byb2'
'9m');

View file

@ -37,6 +37,7 @@ message EncryptedContent {
/// This can be added, so the receiver can check weather he is up to date with the current profile /// This can be added, so the receiver can check weather he is up to date with the current profile
optional int64 sender_profile_counter = 4; optional int64 sender_profile_counter = 4;
optional bytes sender_user_discovery_version = 21; optional bytes sender_user_discovery_version = 21;
optional bool ask_for_friend_promotions = 25;
optional MessageUpdate message_update = 5; optional MessageUpdate message_update = 5;
optional Media media = 6; optional Media media = 6;

View file

@ -19,6 +19,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.service.dart' show UserService;
import 'package:twonly/src/services/user_discovery.service.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';
@ -443,12 +444,26 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
userService.currentUser.avatarCounter, userService.currentUser.avatarCounter,
); );
if (userService.currentUser.isUserDiscoveryEnabled && messageId != null) { {
final contact = await twonlyDB.contactsDao.getContactById(contactId); if (userService.currentUser.askForFriendPromotions) {
if (UserDiscoveryService.isContactAllowed(contact)) { final contacts = await twonlyDB.contactsDao.getAllContacts();
final version = await UserDiscoveryService.getCurrentVersion(); final contactCount = contacts.where((c) => c.accepted).length;
if (version != null) { if (contactCount > 5) {
encryptedContent.senderUserDiscoveryVersion = version; await UserService.update((u) {
u.askForFriendPromotions = false;
});
} else {
encryptedContent.askForFriendPromotions = true;
}
}
if (userService.currentUser.isUserDiscoveryEnabled && messageId != null) {
final contact = await twonlyDB.contactsDao.getContactById(contactId);
if (UserDiscoveryService.isContactAllowed(contact)) {
final version = await UserDiscoveryService.getCurrentVersion();
if (version != null) {
encryptedContent.senderUserDiscoveryVersion = version;
}
} }
} }
} }

View file

@ -320,6 +320,16 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
); );
} }
if (content.hasAskForFriendPromotions() && content.askForFriendPromotions) {
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
if (contact != null && contact.askForFriendPromotions == null) {
await twonlyDB.contactsDao.updateContact(
fromUserId,
const ContactsCompanion(askForFriendPromotions: Value(true)),
);
}
}
if (content.hasContactRequest()) { if (content.hasContactRequest()) {
if (!await handleContactRequest( if (!await handleContactRequest(
fromUserId, fromUserId,

View file

@ -158,9 +158,21 @@ Future<void> runMigrations() async {
} }
await UserService.update((u) => u.appVersion = 116); await UserService.update((u) => u.appVersion = 116);
} }
if (userService.currentUser.appVersion < 117) {
final contacts = await twonlyDB.contactsDao.getAllContacts();
final contactCount = contacts.where((c) => c.accepted).length;
await UserService.update((u) {
u.appVersion = 117;
if (contactCount > 5) {
u.askForFriendPromotions = false;
}
});
}
if (kDebugMode) { if (kDebugMode) {
assert( assert(
AppState.latestAppVersionId == 116, AppState.latestAppVersionId == 117,
'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.', 'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.',
); );
assert( assert(

View file

@ -1,5 +1,7 @@
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:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -42,6 +44,17 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
widget.group.groupId, widget.group.groupId,
selectedContacts, selectedContacts,
); );
if (widget.group.isDirectChat) {
final members = await twonlyDB.groupsDao.getGroupContact(
widget.group.groupId,
);
if (members.isNotEmpty) {
await twonlyDB.contactsDao.updateContact(
members.first.userId,
const ContactsCompanion(askForFriendPromotions: Value(false)),
);
}
}
} }
if (mounted) { if (mounted) {
Navigator.pop(context); Navigator.pop(context);

View file

@ -19,8 +19,10 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart'; import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/unverified_contact_warning.comp.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/message_input_components/ask_for_friend_promotions.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/user_discovery_manual_approval.comp.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/message_input_components/sparks.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_input_components/unverified_contact_warning.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_input_components/user_discovery_manual_approval.comp.dart';
import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart'; import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart';
class MessageInput extends StatefulWidget { class MessageInput extends StatefulWidget {
@ -48,6 +50,7 @@ class _MessageInputState extends State<MessageInput> {
late final RecorderController recorderController; late final RecorderController recorderController;
final bool isApple = Platform.isIOS; final bool isApple = Platform.isIOS;
bool _emojiShowing = false; bool _emojiShowing = false;
bool _showSparks = false;
bool _audioRecordingLock = false; bool _audioRecordingLock = false;
int _currentDuration = 0; int _currentDuration = 0;
double _cancelSlideOffset = 0; double _cancelSlideOffset = 0;
@ -74,6 +77,7 @@ class _MessageInputState extends State<MessageInput> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_textFieldController = TextEditingController(); _textFieldController = TextEditingController();
_textFieldController.addListener(_handleTextChange); _textFieldController.addListener(_handleTextChange);
if (widget.group.draftMessage != null) { if (widget.group.draftMessage != null) {
@ -210,6 +214,23 @@ class _MessageInputState extends State<MessageInput> {
} }
Future<void> _showAdditionalShareModal(BuildContext context) async { Future<void> _showAdditionalShareModal(BuildContext context) async {
setState(() {
_showSparks = false;
});
if (widget.group.isDirectChat) {
final members = await twonlyDB.groupsDao.getGroupContact(
widget.group.groupId,
);
if (members.isNotEmpty) {
await twonlyDB.contactsDao.updateContact(
members.first.userId,
const ContactsCompanion(
askForFriendPromotions: Value(false),
),
);
}
}
if (!context.mounted) return;
// ignore: inference_failure_on_function_invocation // ignore: inference_failure_on_function_invocation
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
@ -240,6 +261,15 @@ class _MessageInputState extends State<MessageInput> {
return Column( return Column(
children: [ children: [
UserDiscoveryManualApprovalComp(group: widget.group), UserDiscoveryManualApprovalComp(group: widget.group),
AskForFriendPromotionsComp(
group: widget.group,
onHighlightChanged: (highlight) {
if (!mounted) return;
setState(() {
_showSparks = highlight;
});
},
),
if (_contactId != null) if (_contactId != null)
Container( Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
@ -261,9 +291,6 @@ class _MessageInputState extends State<MessageInput> {
children: [ children: [
Expanded( Expanded(
child: Container( child: Container(
// padding: const EdgeInsets.symmetric(
// horizontal: 3,
// ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.color.surfaceContainer, color: context.color.surfaceContainer,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
@ -551,10 +578,13 @@ class _MessageInputState extends State<MessageInput> {
: _sendMessage, : _sendMessage,
) )
else else
IconButton( SparksWidget(
icon: const FaIcon(FontAwesomeIcons.plus), animate: _showSparks,
padding: const EdgeInsets.all(15), child: IconButton(
onPressed: () => _showAdditionalShareModal(context), icon: const FaIcon(FontAwesomeIcons.plus),
padding: const EdgeInsets.all(15),
onPressed: () => _showAdditionalShareModal(context),
),
), ),
], ],
), ),

View file

@ -0,0 +1,184 @@
import 'dart:async';
import 'dart:math' show pi;
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
class AskForFriendPromotionsComp extends StatefulWidget {
const AskForFriendPromotionsComp({
required this.group,
this.onHighlightChanged,
super.key,
});
final Group group;
final ValueChanged<bool>? onHighlightChanged;
@override
State<AskForFriendPromotionsComp> createState() =>
_AskForFriendPromotionsCompState();
}
class _AskForFriendPromotionsCompState extends State<AskForFriendPromotionsComp>
with SingleTickerProviderStateMixin {
Contact? _contact;
StreamSubscription<Contact?>? _subscription;
late final AnimationController _arrowController;
late final Animation<double> _arrowAnimation;
@override
void initState() {
super.initState();
_initContactWatcher();
_arrowController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1000),
)..repeat(reverse: true);
_arrowAnimation = Tween<double>(begin: -2, end: 3).animate(
CurvedAnimation(parent: _arrowController, curve: Curves.easeInOut),
);
}
@override
void didUpdateWidget(AskForFriendPromotionsComp oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.group.groupId != widget.group.groupId) {
_initContactWatcher();
}
}
@override
void dispose() {
_subscription?.cancel();
_arrowController.dispose();
// Schedule state callback outside of dispose to avoid setState call during build/dispose issues
final callback = widget.onHighlightChanged;
if (callback != null) {
scheduleMicrotask(() => callback(false));
}
super.dispose();
}
Future<void> _initContactWatcher() async {
await _subscription?.cancel();
if (!widget.group.isDirectChat) {
setState(() {
_contact = null;
});
widget.onHighlightChanged?.call(false);
return;
}
final members = await twonlyDB.groupsDao.getGroupContact(
widget.group.groupId,
);
if (members.isEmpty) {
setState(() {
_contact = null;
});
widget.onHighlightChanged?.call(false);
return;
}
final contactId = members.first.userId;
_subscription = twonlyDB.contactsDao.watchContact(contactId).listen((
contact,
) {
if (mounted) {
setState(() {
_contact = contact;
});
final shouldHighlight = contact?.askForFriendPromotions == true;
widget.onHighlightChanged?.call(shouldHighlight);
}
});
}
@override
Widget build(BuildContext context) {
if (_contact == null || _contact!.askForFriendPromotions != true) {
return const SizedBox.shrink();
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer.withAlpha(40),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: Theme.of(context).colorScheme.primary.withAlpha(60),
width: 0.8,
),
),
child: Row(
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
await twonlyDB.contactsDao.updateContact(
_contact!.userId,
const ContactsCompanion(
askForFriendPromotions: Value(false),
),
);
},
child: Padding(
padding: const EdgeInsets.all(4),
child: FaIcon(
FontAwesomeIcons.xmark,
size: 13,
color: Theme.of(
context,
).colorScheme.onSurfaceVariant.withAlpha(150),
),
),
),
const SizedBox(width: 8),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
context.lang.askForFriendPromotionsPrompt(
_contact!.displayName ?? _contact!.username,
),
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w500,
height: 1.35,
),
),
),
),
const SizedBox(width: 8),
Padding(
padding: const EdgeInsets.only(right: 4),
child: AnimatedBuilder(
animation: _arrowAnimation,
builder: (context, child) {
return Transform.rotate(
angle: -pi / 10,
child: Transform.translate(
offset: Offset(0, _arrowAnimation.value),
child: child,
),
);
},
child: FaIcon(
FontAwesomeIcons.anglesDown,
color: Theme.of(context).colorScheme.primary,
size: 15,
),
),
),
],
),
);
}
}

View file

@ -0,0 +1,150 @@
import 'dart:math';
import 'package:flutter/material.dart';
class SparksWidget extends StatefulWidget {
const SparksWidget({
required this.child,
required this.animate,
super.key,
});
final Widget child;
final bool animate;
@override
State<SparksWidget> createState() => _SparksWidgetState();
}
class _SparksWidgetState extends State<SparksWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
final List<Spark> _sparks = [];
final _random = Random();
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
if (widget.animate) {
_controller.repeat();
_startSparksGenerator();
}
}
@override
void didUpdateWidget(SparksWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.animate != oldWidget.animate) {
if (widget.animate) {
_controller.repeat();
_startSparksGenerator();
} else {
_controller.stop();
_sparks.clear();
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _startSparksGenerator() {
_controller.addListener(_generateAndUpdateSparks);
}
void _generateAndUpdateSparks() {
if (!mounted || !widget.animate) return;
// Update existing sparks
setState(() {
_sparks.removeWhere((s) => s.progress >= 1.0);
for (final spark in _sparks) {
spark.progress += 0.005;
}
// Generate new spark occasionally
if (_sparks.length < 15 && _random.nextDouble() < 0.15) {
final angle = -pi * 0.58 + _random.nextDouble() * pi * 0.16;
final speed = 60 + _random.nextDouble() * 60;
final color = _random.nextBool()
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary;
_sparks.add(
Spark(
angle: angle,
speed: speed,
color: color,
size: 2 + _random.nextDouble() * 3,
progress: 0,
),
);
}
});
}
@override
Widget build(BuildContext context) {
if (!widget.animate) {
return widget.child;
}
return Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
CustomPaint(
painter: SparksPainter(sparks: _sparks),
),
widget.child,
],
);
}
}
class Spark {
Spark({
required this.angle,
required this.speed,
required this.color,
required this.size,
required this.progress,
});
final double angle;
final double speed;
final Color color;
final double size;
double progress;
}
class SparksPainter extends CustomPainter {
SparksPainter({required this.sparks});
final List<Spark> sparks;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint();
for (final spark in sparks) {
final distance = spark.speed * spark.progress;
final dx = cos(spark.angle) * distance;
final dy = sin(spark.angle) * distance;
final alpha = (220 * (1 - spark.progress)).toInt();
paint.color = spark.color.withAlpha(alpha);
canvas.drawCircle(Offset(dx, dy), spark.size, paint);
}
}
@override
bool shouldRepaint(covariant SparksPainter oldDelegate) => true;
}

View file

@ -21,6 +21,7 @@ import 'schema_v14.dart' as v14;
import 'schema_v15.dart' as v15; import 'schema_v15.dart' as v15;
import 'schema_v16.dart' as v16; import 'schema_v16.dart' as v16;
import 'schema_v17.dart' as v17; import 'schema_v17.dart' as v17;
import 'schema_v18.dart' as v18;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -60,6 +61,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v16.DatabaseAtV16(db); return v16.DatabaseAtV16(db);
case 17: case 17:
return v17.DatabaseAtV17(db); return v17.DatabaseAtV17(db);
case 18:
return v18.DatabaseAtV18(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
@ -83,5 +86,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
15, 15,
16, 16,
17, 17,
18,
]; ];
} }

File diff suppressed because it is too large Load diff