share twonly score with friends

This commit is contained in:
otsmr 2026-10-05 22:15:25 +02:00
parent 4337db58be
commit e76eead183
32 changed files with 918 additions and 164 deletions

2
.gitignore vendored
View file

@ -60,4 +60,4 @@ fastlane/repo/status/running.json
# Widget Preview related # Widget Preview related
.widget_preview/ .widget_preview/
ios/build/ ios/build/
APKS/*.apk offline/

View file

@ -8082,6 +8082,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
} }
@protected
TwonlyScoreVisibility dco_decode_twonly_score_visibility(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return TwonlyScoreVisibility.values[raw as int];
}
@protected @protected
int dco_decode_u_16(dynamic raw) { int dco_decode_u_16(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@ -8122,8 +8128,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
UserConfig dco_decode_user_config(dynamic raw) { UserConfig dco_decode_user_config(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>; final arr = raw as List<dynamic>;
if (arr.length != 64) if (arr.length != 65)
throw Exception('unexpected arr length: expect 64 but see ${arr.length}'); throw Exception('unexpected arr length: expect 65 but see ${arr.length}');
return UserConfig( return UserConfig(
userId: dco_decode_i_64(arr[0]), userId: dco_decode_i_64(arr[0]),
username: dco_decode_String(arr[1]), username: dco_decode_String(arr[1]),
@ -8169,31 +8175,32 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
allowErrorTrackingViaSentry: dco_decode_bool(arr[37]), allowErrorTrackingViaSentry: dco_decode_bool(arr[37]),
screenLockEnabled: dco_decode_bool(arr[38]), screenLockEnabled: dco_decode_bool(arr[38]),
isCloudBackupEnabled: dco_decode_bool(arr[39]), isCloudBackupEnabled: dco_decode_bool(arr[39]),
isUserDiscoveryEnabled: dco_decode_bool(arr[40]), twonlyScoreVisibility: dco_decode_twonly_score_visibility(arr[40]),
requiredSendImages: dco_decode_i_64(arr[41]), isUserDiscoveryEnabled: dco_decode_bool(arr[41]),
userDiscoveryThreshold: dco_decode_u_8(arr[42]), requiredSendImages: dco_decode_i_64(arr[42]),
userDiscoveryRequiresManualApproval: dco_decode_bool(arr[43]), userDiscoveryThreshold: dco_decode_u_8(arr[43]),
userDiscoverySharePromotion: dco_decode_bool(arr[44]), userDiscoveryRequiresManualApproval: dco_decode_bool(arr[44]),
userDiscoveryInitializationError: dco_decode_bool(arr[45]), userDiscoverySharePromotion: dco_decode_bool(arr[45]),
askForFriendPromotions: dco_decode_bool(arr[46]), userDiscoveryInitializationError: dco_decode_bool(arr[46]),
currentPreKeyIndexStart: dco_decode_i_64(arr[47]), askForFriendPromotions: dco_decode_bool(arr[47]),
currentSignedPreKeyIndexStart: dco_decode_i_64(arr[48]), currentPreKeyIndexStart: dco_decode_i_64(arr[48]),
lastChangeLogHash: dco_decode_opt_list_prim_u_8_strict(arr[49]), currentSignedPreKeyIndexStart: dco_decode_i_64(arr[49]),
hideChangeLog: dco_decode_bool(arr[50]), lastChangeLogHash: dco_decode_opt_list_prim_u_8_strict(arr[50]),
hideMemoriesBackupPromo: dco_decode_bool(arr[51]), hideChangeLog: dco_decode_bool(arr[51]),
hideWidgetShareExplainer: dco_decode_bool(arr[52]), hideMemoriesBackupPromo: dco_decode_bool(arr[52]),
updateFcmToken: dco_decode_bool(arr[53]), hideWidgetShareExplainer: dco_decode_bool(arr[53]),
canUseLoginTokenForAuth: dco_decode_bool(arr[54]), updateFcmToken: dco_decode_bool(arr[54]),
twonlySafeBackup: dco_decode_opt_box_autoadd_twonly_safe_backup(arr[55]), canUseLoginTokenForAuth: dco_decode_bool(arr[55]),
isBackupEnabled: dco_decode_bool(arr[56]), twonlySafeBackup: dco_decode_opt_box_autoadd_twonly_safe_backup(arr[56]),
isBackupEnabled: dco_decode_bool(arr[57]),
passwordLessRecovery: passwordLessRecovery:
dco_decode_opt_box_autoadd_passwordless_recovery_config(arr[57]), dco_decode_opt_box_autoadd_passwordless_recovery_config(arr[58]),
fcmToken: dco_decode_opt_String(arr[58]), fcmToken: dco_decode_opt_String(arr[59]),
lastFcmWakeupAt: dco_decode_opt_box_autoadd_i_64(arr[59]), lastFcmWakeupAt: dco_decode_opt_box_autoadd_i_64(arr[60]),
lastServerMessageAt: dco_decode_opt_box_autoadd_i_64(arr[60]), lastServerMessageAt: dco_decode_opt_box_autoadd_i_64(arr[61]),
currentSetupPage: dco_decode_opt_String(arr[61]), currentSetupPage: dco_decode_opt_String(arr[62]),
skipSetupPages: dco_decode_bool(arr[62]), skipSetupPages: dco_decode_bool(arr[63]),
hasZoomed: dco_decode_bool(arr[63]), hasZoomed: dco_decode_bool(arr[64]),
); );
} }
@ -9449,6 +9456,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
} }
@protected
TwonlyScoreVisibility sse_decode_twonly_score_visibility(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
var inner = sse_decode_i_32(deserializer);
return TwonlyScoreVisibility.values[inner];
}
@protected @protected
int sse_decode_u_16(SseDeserializer deserializer) { int sse_decode_u_16(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@ -9534,6 +9550,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_allowErrorTrackingViaSentry = sse_decode_bool(deserializer); var var_allowErrorTrackingViaSentry = sse_decode_bool(deserializer);
var var_screenLockEnabled = sse_decode_bool(deserializer); var var_screenLockEnabled = sse_decode_bool(deserializer);
var var_isCloudBackupEnabled = sse_decode_bool(deserializer); var var_isCloudBackupEnabled = sse_decode_bool(deserializer);
var var_twonlyScoreVisibility = sse_decode_twonly_score_visibility(
deserializer,
);
var var_isUserDiscoveryEnabled = sse_decode_bool(deserializer); var var_isUserDiscoveryEnabled = sse_decode_bool(deserializer);
var var_requiredSendImages = sse_decode_i_64(deserializer); var var_requiredSendImages = sse_decode_i_64(deserializer);
var var_userDiscoveryThreshold = sse_decode_u_8(deserializer); var var_userDiscoveryThreshold = sse_decode_u_8(deserializer);
@ -9606,6 +9625,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
allowErrorTrackingViaSentry: var_allowErrorTrackingViaSentry, allowErrorTrackingViaSentry: var_allowErrorTrackingViaSentry,
screenLockEnabled: var_screenLockEnabled, screenLockEnabled: var_screenLockEnabled,
isCloudBackupEnabled: var_isCloudBackupEnabled, isCloudBackupEnabled: var_isCloudBackupEnabled,
twonlyScoreVisibility: var_twonlyScoreVisibility,
isUserDiscoveryEnabled: var_isUserDiscoveryEnabled, isUserDiscoveryEnabled: var_isUserDiscoveryEnabled,
requiredSendImages: var_requiredSendImages, requiredSendImages: var_requiredSendImages,
userDiscoveryThreshold: var_userDiscoveryThreshold, userDiscoveryThreshold: var_userDiscoveryThreshold,
@ -10856,6 +10876,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_list_prim_u_8_strict(self.encryptionKey, serializer); sse_encode_list_prim_u_8_strict(self.encryptionKey, serializer);
} }
@protected
void sse_encode_twonly_score_visibility(
TwonlyScoreVisibility self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_i_32(self.index, serializer);
}
@protected @protected
void sse_encode_u_16(int self, SseSerializer serializer) { void sse_encode_u_16(int self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
@ -10943,6 +10972,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_bool(self.allowErrorTrackingViaSentry, serializer); sse_encode_bool(self.allowErrorTrackingViaSentry, serializer);
sse_encode_bool(self.screenLockEnabled, serializer); sse_encode_bool(self.screenLockEnabled, serializer);
sse_encode_bool(self.isCloudBackupEnabled, serializer); sse_encode_bool(self.isCloudBackupEnabled, serializer);
sse_encode_twonly_score_visibility(self.twonlyScoreVisibility, serializer);
sse_encode_bool(self.isUserDiscoveryEnabled, serializer); sse_encode_bool(self.isUserDiscoveryEnabled, serializer);
sse_encode_i_64(self.requiredSendImages, serializer); sse_encode_i_64(self.requiredSendImages, serializer);
sse_encode_u_8(self.userDiscoveryThreshold, serializer); sse_encode_u_8(self.userDiscoveryThreshold, serializer);

View file

@ -396,6 +396,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
TwonlySafeBackup dco_decode_twonly_safe_backup(dynamic raw); TwonlySafeBackup dco_decode_twonly_safe_backup(dynamic raw);
@protected
TwonlyScoreVisibility dco_decode_twonly_score_visibility(dynamic raw);
@protected @protected
int dco_decode_u_16(dynamic raw); int dco_decode_u_16(dynamic raw);
@ -863,6 +866,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
TwonlySafeBackup sse_decode_twonly_safe_backup(SseDeserializer deserializer); TwonlySafeBackup sse_decode_twonly_safe_backup(SseDeserializer deserializer);
@protected
TwonlyScoreVisibility sse_decode_twonly_score_visibility(
SseDeserializer deserializer,
);
@protected @protected
int sse_decode_u_16(SseDeserializer deserializer); int sse_decode_u_16(SseDeserializer deserializer);
@ -1454,6 +1462,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_twonly_score_visibility(
TwonlyScoreVisibility self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_u_16(int self, SseSerializer serializer); void sse_encode_u_16(int self, SseSerializer serializer);

View file

@ -398,6 +398,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
TwonlySafeBackup dco_decode_twonly_safe_backup(dynamic raw); TwonlySafeBackup dco_decode_twonly_safe_backup(dynamic raw);
@protected
TwonlyScoreVisibility dco_decode_twonly_score_visibility(dynamic raw);
@protected @protected
int dco_decode_u_16(dynamic raw); int dco_decode_u_16(dynamic raw);
@ -865,6 +868,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
TwonlySafeBackup sse_decode_twonly_safe_backup(SseDeserializer deserializer); TwonlySafeBackup sse_decode_twonly_safe_backup(SseDeserializer deserializer);
@protected
TwonlyScoreVisibility sse_decode_twonly_score_visibility(
SseDeserializer deserializer,
);
@protected @protected
int sse_decode_u_16(SseDeserializer deserializer); int sse_decode_u_16(SseDeserializer deserializer);
@ -1456,6 +1464,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_twonly_score_visibility(
TwonlyScoreVisibility self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_u_16(int self, SseSerializer serializer); void sse_encode_u_16(int self, SseSerializer serializer);

View file

@ -102,6 +102,12 @@ class TwonlySafeBackup {
encryptionKey == other.encryptionKey; encryptionKey == other.encryptionKey;
} }
enum TwonlyScoreVisibility {
nobody,
onlyContacts,
everyone,
}
class UserConfig { class UserConfig {
PlatformInt64 userId; PlatformInt64 userId;
String username; String username;
@ -155,6 +161,9 @@ class UserConfig {
bool allowErrorTrackingViaSentry; bool allowErrorTrackingViaSentry;
bool screenLockEnabled; bool screenLockEnabled;
bool isCloudBackupEnabled; bool isCloudBackupEnabled;
/// Controls which recipients receive the current twonly Score in sender metadata.
TwonlyScoreVisibility twonlyScoreVisibility;
bool isUserDiscoveryEnabled; bool isUserDiscoveryEnabled;
PlatformInt64 requiredSendImages; PlatformInt64 requiredSendImages;
int userDiscoveryThreshold; int userDiscoveryThreshold;
@ -229,6 +238,7 @@ class UserConfig {
required this.allowErrorTrackingViaSentry, required this.allowErrorTrackingViaSentry,
required this.screenLockEnabled, required this.screenLockEnabled,
required this.isCloudBackupEnabled, required this.isCloudBackupEnabled,
required this.twonlyScoreVisibility,
required this.isUserDiscoveryEnabled, required this.isUserDiscoveryEnabled,
required this.requiredSendImages, required this.requiredSendImages,
required this.userDiscoveryThreshold, required this.userDiscoveryThreshold,
@ -297,6 +307,7 @@ class UserConfig {
allowErrorTrackingViaSentry.hashCode ^ allowErrorTrackingViaSentry.hashCode ^
screenLockEnabled.hashCode ^ screenLockEnabled.hashCode ^
isCloudBackupEnabled.hashCode ^ isCloudBackupEnabled.hashCode ^
twonlyScoreVisibility.hashCode ^
isUserDiscoveryEnabled.hashCode ^ isUserDiscoveryEnabled.hashCode ^
requiredSendImages.hashCode ^ requiredSendImages.hashCode ^
userDiscoveryThreshold.hashCode ^ userDiscoveryThreshold.hashCode ^
@ -371,6 +382,7 @@ class UserConfig {
allowErrorTrackingViaSentry == other.allowErrorTrackingViaSentry && allowErrorTrackingViaSentry == other.allowErrorTrackingViaSentry &&
screenLockEnabled == other.screenLockEnabled && screenLockEnabled == other.screenLockEnabled &&
isCloudBackupEnabled == other.isCloudBackupEnabled && isCloudBackupEnabled == other.isCloudBackupEnabled &&
twonlyScoreVisibility == other.twonlyScoreVisibility &&
isUserDiscoveryEnabled == other.isUserDiscoveryEnabled && isUserDiscoveryEnabled == other.isUserDiscoveryEnabled &&
requiredSendImages == other.requiredSendImages && requiredSendImages == other.requiredSendImages &&
userDiscoveryThreshold == other.userDiscoveryThreshold && userDiscoveryThreshold == other.userDiscoveryThreshold &&

View file

@ -42,6 +42,8 @@ class Routes {
'/settings/privacy/block_users'; '/settings/privacy/block_users';
static const String settingsPrivacyUserDiscovery = static const String settingsPrivacyUserDiscovery =
'/settings/privacy/user_discovery'; '/settings/privacy/user_discovery';
static const String settingsPrivacyPublicProfile =
'/settings/privacy/public_profile';
static const String settingsNotification = '/settings/notification'; static const String settingsNotification = '/settings/notification';
static const String settingsStorage = '/settings/storage_data'; static const String settingsStorage = '/settings/storage_data';
static const String settingsStorageManage = '/settings/storage_data/manage'; static const String settingsStorageManage = '/settings/storage_data/manage';

View file

@ -53,6 +53,8 @@ class Contacts extends Table {
BoolColumn get widgetSharingGranted => BoolColumn get widgetSharingGranted =>
boolean().withDefault(const Constant(false))(); boolean().withDefault(const Constant(false))();
IntColumn get twonlyScore => integer().nullable()();
IntColumn get mediaSendCounter => integer().withDefault(const Constant(0))(); IntColumn get mediaSendCounter => integer().withDefault(const Constant(0))();
IntColumn get mediaReceivedCounter => IntColumn get mediaReceivedCounter =>
integer().withDefault(const Constant(0))(); integer().withDefault(const Constant(0))();

View file

@ -337,6 +337,17 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
), ),
defaultValue: const Constant(false), defaultValue: const Constant(false),
); );
static const VerificationMeta _twonlyScoreMeta = const VerificationMeta(
'twonlyScore',
);
@override
late final GeneratedColumn<int> twonlyScore = GeneratedColumn<int>(
'twonly_score',
aliasedName,
true,
type: DriftSqlType.int,
requiredDuringInsert: false,
);
static const VerificationMeta _mediaSendCounterMeta = const VerificationMeta( static const VerificationMeta _mediaSendCounterMeta = const VerificationMeta(
'mediaSendCounter', 'mediaSendCounter',
); );
@ -388,6 +399,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
askForFriendPromotions, askForFriendPromotions,
widgetSharingAllowed, widgetSharingAllowed,
widgetSharingGranted, widgetSharingGranted,
twonlyScore,
mediaSendCounter, mediaSendCounter,
mediaReceivedCounter, mediaReceivedCounter,
]; ];
@ -606,6 +618,15 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
), ),
); );
} }
if (data.containsKey('twonly_score')) {
context.handle(
_twonlyScoreMeta,
twonlyScore.isAcceptableOrUnknown(
data['twonly_score']!,
_twonlyScoreMeta,
),
);
}
if (data.containsKey('media_send_counter')) { if (data.containsKey('media_send_counter')) {
context.handle( context.handle(
_mediaSendCounterMeta, _mediaSendCounterMeta,
@ -739,6 +760,10 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
DriftSqlType.bool, DriftSqlType.bool,
data['${effectivePrefix}widget_sharing_granted'], data['${effectivePrefix}widget_sharing_granted'],
)!, )!,
twonlyScore: attachedDatabase.typeMapping.read(
DriftSqlType.int,
data['${effectivePrefix}twonly_score'],
),
mediaSendCounter: attachedDatabase.typeMapping.read( mediaSendCounter: attachedDatabase.typeMapping.read(
DriftSqlType.int, DriftSqlType.int,
data['${effectivePrefix}media_send_counter'], data['${effectivePrefix}media_send_counter'],
@ -788,6 +813,7 @@ class Contact extends DataClass implements Insertable<Contact> {
final bool? askForFriendPromotions; final bool? askForFriendPromotions;
final bool widgetSharingAllowed; final bool widgetSharingAllowed;
final bool widgetSharingGranted; final bool widgetSharingGranted;
final int? twonlyScore;
final int mediaSendCounter; final int mediaSendCounter;
final int mediaReceivedCounter; final int mediaReceivedCounter;
const Contact({ const Contact({
@ -817,6 +843,7 @@ class Contact extends DataClass implements Insertable<Contact> {
this.askForFriendPromotions, this.askForFriendPromotions,
required this.widgetSharingAllowed, required this.widgetSharingAllowed,
required this.widgetSharingGranted, required this.widgetSharingGranted,
this.twonlyScore,
required this.mediaSendCounter, required this.mediaSendCounter,
required this.mediaReceivedCounter, required this.mediaReceivedCounter,
}); });
@ -885,6 +912,9 @@ class Contact extends DataClass implements Insertable<Contact> {
} }
map['widget_sharing_allowed'] = Variable<bool>(widgetSharingAllowed); map['widget_sharing_allowed'] = Variable<bool>(widgetSharingAllowed);
map['widget_sharing_granted'] = Variable<bool>(widgetSharingGranted); map['widget_sharing_granted'] = Variable<bool>(widgetSharingGranted);
if (!nullToAbsent || twonlyScore != null) {
map['twonly_score'] = Variable<int>(twonlyScore);
}
map['media_send_counter'] = Variable<int>(mediaSendCounter); map['media_send_counter'] = Variable<int>(mediaSendCounter);
map['media_received_counter'] = Variable<int>(mediaReceivedCounter); map['media_received_counter'] = Variable<int>(mediaReceivedCounter);
return map; return map;
@ -944,6 +974,9 @@ class Contact extends DataClass implements Insertable<Contact> {
: Value(askForFriendPromotions), : Value(askForFriendPromotions),
widgetSharingAllowed: Value(widgetSharingAllowed), widgetSharingAllowed: Value(widgetSharingAllowed),
widgetSharingGranted: Value(widgetSharingGranted), widgetSharingGranted: Value(widgetSharingGranted),
twonlyScore: twonlyScore == null && nullToAbsent
? const Value.absent()
: Value(twonlyScore),
mediaSendCounter: Value(mediaSendCounter), mediaSendCounter: Value(mediaSendCounter),
mediaReceivedCounter: Value(mediaReceivedCounter), mediaReceivedCounter: Value(mediaReceivedCounter),
); );
@ -1011,6 +1044,7 @@ class Contact extends DataClass implements Insertable<Contact> {
widgetSharingGranted: serializer.fromJson<bool>( widgetSharingGranted: serializer.fromJson<bool>(
json['widgetSharingGranted'], json['widgetSharingGranted'],
), ),
twonlyScore: serializer.fromJson<int?>(json['twonlyScore']),
mediaSendCounter: serializer.fromJson<int>(json['mediaSendCounter']), mediaSendCounter: serializer.fromJson<int>(json['mediaSendCounter']),
mediaReceivedCounter: serializer.fromJson<int>( mediaReceivedCounter: serializer.fromJson<int>(
json['mediaReceivedCounter'], json['mediaReceivedCounter'],
@ -1065,6 +1099,7 @@ class Contact extends DataClass implements Insertable<Contact> {
), ),
'widgetSharingAllowed': serializer.toJson<bool>(widgetSharingAllowed), 'widgetSharingAllowed': serializer.toJson<bool>(widgetSharingAllowed),
'widgetSharingGranted': serializer.toJson<bool>(widgetSharingGranted), 'widgetSharingGranted': serializer.toJson<bool>(widgetSharingGranted),
'twonlyScore': serializer.toJson<int?>(twonlyScore),
'mediaSendCounter': serializer.toJson<int>(mediaSendCounter), 'mediaSendCounter': serializer.toJson<int>(mediaSendCounter),
'mediaReceivedCounter': serializer.toJson<int>(mediaReceivedCounter), 'mediaReceivedCounter': serializer.toJson<int>(mediaReceivedCounter),
}; };
@ -1097,6 +1132,7 @@ class Contact extends DataClass implements Insertable<Contact> {
Value<bool?> askForFriendPromotions = const Value.absent(), Value<bool?> askForFriendPromotions = const Value.absent(),
bool? widgetSharingAllowed, bool? widgetSharingAllowed,
bool? widgetSharingGranted, bool? widgetSharingGranted,
Value<int?> twonlyScore = const Value.absent(),
int? mediaSendCounter, int? mediaSendCounter,
int? mediaReceivedCounter, int? mediaReceivedCounter,
}) => Contact( }) => Contact(
@ -1145,6 +1181,7 @@ class Contact extends DataClass implements Insertable<Contact> {
: this.askForFriendPromotions, : this.askForFriendPromotions,
widgetSharingAllowed: widgetSharingAllowed ?? this.widgetSharingAllowed, widgetSharingAllowed: widgetSharingAllowed ?? this.widgetSharingAllowed,
widgetSharingGranted: widgetSharingGranted ?? this.widgetSharingGranted, widgetSharingGranted: widgetSharingGranted ?? this.widgetSharingGranted,
twonlyScore: twonlyScore.present ? twonlyScore.value : this.twonlyScore,
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter, mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter, mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter,
); );
@ -1212,6 +1249,9 @@ class Contact extends DataClass implements Insertable<Contact> {
widgetSharingGranted: data.widgetSharingGranted.present widgetSharingGranted: data.widgetSharingGranted.present
? data.widgetSharingGranted.value ? data.widgetSharingGranted.value
: this.widgetSharingGranted, : this.widgetSharingGranted,
twonlyScore: data.twonlyScore.present
? data.twonlyScore.value
: this.twonlyScore,
mediaSendCounter: data.mediaSendCounter.present mediaSendCounter: data.mediaSendCounter.present
? data.mediaSendCounter.value ? data.mediaSendCounter.value
: this.mediaSendCounter, : this.mediaSendCounter,
@ -1252,6 +1292,7 @@ class Contact extends DataClass implements Insertable<Contact> {
..write('askForFriendPromotions: $askForFriendPromotions, ') ..write('askForFriendPromotions: $askForFriendPromotions, ')
..write('widgetSharingAllowed: $widgetSharingAllowed, ') ..write('widgetSharingAllowed: $widgetSharingAllowed, ')
..write('widgetSharingGranted: $widgetSharingGranted, ') ..write('widgetSharingGranted: $widgetSharingGranted, ')
..write('twonlyScore: $twonlyScore, ')
..write('mediaSendCounter: $mediaSendCounter, ') ..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter') ..write('mediaReceivedCounter: $mediaReceivedCounter')
..write(')')) ..write(')'))
@ -1286,6 +1327,7 @@ class Contact extends DataClass implements Insertable<Contact> {
askForFriendPromotions, askForFriendPromotions,
widgetSharingAllowed, widgetSharingAllowed,
widgetSharingGranted, widgetSharingGranted,
twonlyScore,
mediaSendCounter, mediaSendCounter,
mediaReceivedCounter, mediaReceivedCounter,
]); ]);
@ -1333,6 +1375,7 @@ class Contact extends DataClass implements Insertable<Contact> {
other.askForFriendPromotions == this.askForFriendPromotions && other.askForFriendPromotions == this.askForFriendPromotions &&
other.widgetSharingAllowed == this.widgetSharingAllowed && other.widgetSharingAllowed == this.widgetSharingAllowed &&
other.widgetSharingGranted == this.widgetSharingGranted && other.widgetSharingGranted == this.widgetSharingGranted &&
other.twonlyScore == this.twonlyScore &&
other.mediaSendCounter == this.mediaSendCounter && other.mediaSendCounter == this.mediaSendCounter &&
other.mediaReceivedCounter == this.mediaReceivedCounter); other.mediaReceivedCounter == this.mediaReceivedCounter);
} }
@ -1364,6 +1407,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
final Value<bool?> askForFriendPromotions; final Value<bool?> askForFriendPromotions;
final Value<bool> widgetSharingAllowed; final Value<bool> widgetSharingAllowed;
final Value<bool> widgetSharingGranted; final Value<bool> widgetSharingGranted;
final Value<int?> twonlyScore;
final Value<int> mediaSendCounter; final Value<int> mediaSendCounter;
final Value<int> mediaReceivedCounter; final Value<int> mediaReceivedCounter;
const ContactsCompanion({ const ContactsCompanion({
@ -1393,6 +1437,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
this.askForFriendPromotions = const Value.absent(), this.askForFriendPromotions = const Value.absent(),
this.widgetSharingAllowed = const Value.absent(), this.widgetSharingAllowed = const Value.absent(),
this.widgetSharingGranted = const Value.absent(), this.widgetSharingGranted = const Value.absent(),
this.twonlyScore = const Value.absent(),
this.mediaSendCounter = const Value.absent(), this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(), this.mediaReceivedCounter = const Value.absent(),
}); });
@ -1423,6 +1468,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
this.askForFriendPromotions = const Value.absent(), this.askForFriendPromotions = const Value.absent(),
this.widgetSharingAllowed = const Value.absent(), this.widgetSharingAllowed = const Value.absent(),
this.widgetSharingGranted = const Value.absent(), this.widgetSharingGranted = const Value.absent(),
this.twonlyScore = const Value.absent(),
this.mediaSendCounter = const Value.absent(), this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(), this.mediaReceivedCounter = const Value.absent(),
}) : username = Value(username); }) : username = Value(username);
@ -1453,6 +1499,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Expression<bool>? askForFriendPromotions, Expression<bool>? askForFriendPromotions,
Expression<bool>? widgetSharingAllowed, Expression<bool>? widgetSharingAllowed,
Expression<bool>? widgetSharingGranted, Expression<bool>? widgetSharingGranted,
Expression<int>? twonlyScore,
Expression<int>? mediaSendCounter, Expression<int>? mediaSendCounter,
Expression<int>? mediaReceivedCounter, Expression<int>? mediaReceivedCounter,
}) { }) {
@ -1497,6 +1544,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
'widget_sharing_allowed': widgetSharingAllowed, 'widget_sharing_allowed': widgetSharingAllowed,
if (widgetSharingGranted != null) if (widgetSharingGranted != null)
'widget_sharing_granted': widgetSharingGranted, 'widget_sharing_granted': widgetSharingGranted,
if (twonlyScore != null) 'twonly_score': twonlyScore,
if (mediaSendCounter != null) 'media_send_counter': mediaSendCounter, if (mediaSendCounter != null) 'media_send_counter': mediaSendCounter,
if (mediaReceivedCounter != null) if (mediaReceivedCounter != null)
'media_received_counter': mediaReceivedCounter, 'media_received_counter': mediaReceivedCounter,
@ -1530,6 +1578,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Value<bool?>? askForFriendPromotions, Value<bool?>? askForFriendPromotions,
Value<bool>? widgetSharingAllowed, Value<bool>? widgetSharingAllowed,
Value<bool>? widgetSharingGranted, Value<bool>? widgetSharingGranted,
Value<int?>? twonlyScore,
Value<int>? mediaSendCounter, Value<int>? mediaSendCounter,
Value<int>? mediaReceivedCounter, Value<int>? mediaReceivedCounter,
}) { }) {
@ -1568,6 +1617,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
askForFriendPromotions ?? this.askForFriendPromotions, askForFriendPromotions ?? this.askForFriendPromotions,
widgetSharingAllowed: widgetSharingAllowed ?? this.widgetSharingAllowed, widgetSharingAllowed: widgetSharingAllowed ?? this.widgetSharingAllowed,
widgetSharingGranted: widgetSharingGranted ?? this.widgetSharingGranted, widgetSharingGranted: widgetSharingGranted ?? this.widgetSharingGranted,
twonlyScore: twonlyScore ?? this.twonlyScore,
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter, mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter, mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter,
); );
@ -1682,6 +1732,9 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
widgetSharingGranted.value, widgetSharingGranted.value,
); );
} }
if (twonlyScore.present) {
map['twonly_score'] = Variable<int>(twonlyScore.value);
}
if (mediaSendCounter.present) { if (mediaSendCounter.present) {
map['media_send_counter'] = Variable<int>(mediaSendCounter.value); map['media_send_counter'] = Variable<int>(mediaSendCounter.value);
} }
@ -1722,6 +1775,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
..write('askForFriendPromotions: $askForFriendPromotions, ') ..write('askForFriendPromotions: $askForFriendPromotions, ')
..write('widgetSharingAllowed: $widgetSharingAllowed, ') ..write('widgetSharingAllowed: $widgetSharingAllowed, ')
..write('widgetSharingGranted: $widgetSharingGranted, ') ..write('widgetSharingGranted: $widgetSharingGranted, ')
..write('twonlyScore: $twonlyScore, ')
..write('mediaSendCounter: $mediaSendCounter, ') ..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter') ..write('mediaReceivedCounter: $mediaReceivedCounter')
..write(')')) ..write(')'))
@ -15757,6 +15811,7 @@ typedef $$ContactsTableCreateCompanionBuilder =
Value<bool?> askForFriendPromotions, Value<bool?> askForFriendPromotions,
Value<bool> widgetSharingAllowed, Value<bool> widgetSharingAllowed,
Value<bool> widgetSharingGranted, Value<bool> widgetSharingGranted,
Value<int?> twonlyScore,
Value<int> mediaSendCounter, Value<int> mediaSendCounter,
Value<int> mediaReceivedCounter, Value<int> mediaReceivedCounter,
}); });
@ -15788,6 +15843,7 @@ typedef $$ContactsTableUpdateCompanionBuilder =
Value<bool?> askForFriendPromotions, Value<bool?> askForFriendPromotions,
Value<bool> widgetSharingAllowed, Value<bool> widgetSharingAllowed,
Value<bool> widgetSharingGranted, Value<bool> widgetSharingGranted,
Value<int?> twonlyScore,
Value<int> mediaSendCounter, Value<int> mediaSendCounter,
Value<int> mediaReceivedCounter, Value<int> mediaReceivedCounter,
}); });
@ -16207,6 +16263,11 @@ class $$ContactsTableFilterComposer
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<int> get twonlyScore => $composableBuilder(
column: $table.twonlyScore,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<int> get mediaSendCounter => $composableBuilder( ColumnFilters<int> get mediaSendCounter => $composableBuilder(
column: $table.mediaSendCounter, column: $table.mediaSendCounter,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
@ -16667,6 +16728,11 @@ class $$ContactsTableOrderingComposer
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<int> get twonlyScore => $composableBuilder(
column: $table.twonlyScore,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<int> get mediaSendCounter => $composableBuilder( ColumnOrderings<int> get mediaSendCounter => $composableBuilder(
column: $table.mediaSendCounter, column: $table.mediaSendCounter,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
@ -16804,6 +16870,11 @@ class $$ContactsTableAnnotationComposer
builder: (column) => column, builder: (column) => column,
); );
GeneratedColumn<int> get twonlyScore => $composableBuilder(
column: $table.twonlyScore,
builder: (column) => column,
);
GeneratedColumn<int> get mediaSendCounter => $composableBuilder( GeneratedColumn<int> get mediaSendCounter => $composableBuilder(
column: $table.mediaSendCounter, column: $table.mediaSendCounter,
builder: (column) => column, builder: (column) => column,
@ -17198,6 +17269,7 @@ class $$ContactsTableTableManager
Value<bool?> askForFriendPromotions = const Value.absent(), Value<bool?> askForFriendPromotions = const Value.absent(),
Value<bool> widgetSharingAllowed = const Value.absent(), Value<bool> widgetSharingAllowed = const Value.absent(),
Value<bool> widgetSharingGranted = const Value.absent(), Value<bool> widgetSharingGranted = const Value.absent(),
Value<int?> twonlyScore = 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(),
}) => ContactsCompanion( }) => ContactsCompanion(
@ -17227,6 +17299,7 @@ class $$ContactsTableTableManager
askForFriendPromotions: askForFriendPromotions, askForFriendPromotions: askForFriendPromotions,
widgetSharingAllowed: widgetSharingAllowed, widgetSharingAllowed: widgetSharingAllowed,
widgetSharingGranted: widgetSharingGranted, widgetSharingGranted: widgetSharingGranted,
twonlyScore: twonlyScore,
mediaSendCounter: mediaSendCounter, mediaSendCounter: mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter, mediaReceivedCounter: mediaReceivedCounter,
), ),
@ -17260,6 +17333,7 @@ class $$ContactsTableTableManager
Value<bool?> askForFriendPromotions = const Value.absent(), Value<bool?> askForFriendPromotions = const Value.absent(),
Value<bool> widgetSharingAllowed = const Value.absent(), Value<bool> widgetSharingAllowed = const Value.absent(),
Value<bool> widgetSharingGranted = const Value.absent(), Value<bool> widgetSharingGranted = const Value.absent(),
Value<int?> twonlyScore = 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(),
}) => ContactsCompanion.insert( }) => ContactsCompanion.insert(
@ -17289,6 +17363,7 @@ class $$ContactsTableTableManager
askForFriendPromotions: askForFriendPromotions, askForFriendPromotions: askForFriendPromotions,
widgetSharingAllowed: widgetSharingAllowed, widgetSharingAllowed: widgetSharingAllowed,
widgetSharingGranted: widgetSharingGranted, widgetSharingGranted: widgetSharingGranted,
twonlyScore: twonlyScore,
mediaSendCounter: mediaSendCounter, mediaSendCounter: mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter, mediaReceivedCounter: mediaReceivedCounter,
), ),

View file

@ -658,6 +658,48 @@ abstract class AppLocalizations {
/// **'{len} contact(s)'** /// **'{len} contact(s)'**
String settingsPrivacyBlockUsersCount(Object len); String settingsPrivacyBlockUsersCount(Object len);
/// No description provided for @publicProfilePrivacyTitle.
///
/// In en, this message translates to:
/// **'Public Profile'**
String get publicProfilePrivacyTitle;
/// No description provided for @publicProfilePrivacyDescription.
///
/// In en, this message translates to:
/// **'Choose who can see information about your profile.'**
String get publicProfilePrivacyDescription;
/// No description provided for @publicProfileTwonlyScore.
///
/// In en, this message translates to:
/// **'twonly Score'**
String get publicProfileTwonlyScore;
/// No description provided for @contactTwonlyScoreDescription.
///
/// In en, this message translates to:
/// **'The twonly Score reflects a person\'s activity on twonly. It is calculated from several factors, such as how many photos and videos they send and receive.'**
String get contactTwonlyScoreDescription;
/// No description provided for @privacyVisibilityNobody.
///
/// In en, this message translates to:
/// **'Nobody'**
String get privacyVisibilityNobody;
/// No description provided for @privacyVisibilityOnlyContacts.
///
/// In en, this message translates to:
/// **'Only Contacts'**
String get privacyVisibilityOnlyContacts;
/// No description provided for @privacyVisibilityEveryone.
///
/// In en, this message translates to:
/// **'Everyone'**
String get privacyVisibilityEveryone;
/// No description provided for @settingsNotification. /// No description provided for @settingsNotification.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -302,6 +302,29 @@ class AppLocalizationsAr extends AppLocalizations {
return 'جهات الاتصال: $len'; return 'جهات الاتصال: $len';
} }
@override
String get publicProfilePrivacyTitle => 'الملف الشخصي العام';
@override
String get publicProfilePrivacyDescription =>
'اختر من يمكنه رؤية معلومات ملفك الشخصي.';
@override
String get publicProfileTwonlyScore => 'نقاط twonly';
@override
String get contactTwonlyScoreDescription =>
'تعكس نقاط twonly نشاط الشخص على twonly. ويتم احتسابها استنادًا إلى عدة عوامل، مثل عدد الصور ومقاطع الفيديو التي يرسلها ويستلمها.';
@override
String get privacyVisibilityNobody => 'لا أحد';
@override
String get privacyVisibilityOnlyContacts => 'جهات الاتصال فقط';
@override
String get privacyVisibilityEveryone => 'الجميع';
@override @override
String get settingsNotification => 'الإشعارات'; String get settingsNotification => 'الإشعارات';

View file

@ -305,6 +305,29 @@ class AppLocalizationsDe extends AppLocalizations {
return '$len Kontakt(e)'; return '$len Kontakt(e)';
} }
@override
String get publicProfilePrivacyTitle => 'Öffentliches Profil';
@override
String get publicProfilePrivacyDescription =>
'Wähle aus, wer Informationen über dein Profil sehen kann.';
@override
String get publicProfileTwonlyScore => 'twonly Score';
@override
String get contactTwonlyScoreDescription =>
'Der twonly Score spiegelt die Aktivität einer Person auf twonly wider. Er wird aus verschiedenen Faktoren berechnet, zum Beispiel daraus, wie viele Fotos und Videos sie sendet und empfängt.';
@override
String get privacyVisibilityNobody => 'Niemand';
@override
String get privacyVisibilityOnlyContacts => 'Nur Kontakte';
@override
String get privacyVisibilityEveryone => 'Alle';
@override @override
String get settingsNotification => 'Benachrichtigung'; String get settingsNotification => 'Benachrichtigung';

View file

@ -302,6 +302,29 @@ class AppLocalizationsEn extends AppLocalizations {
return '$len contact(s)'; return '$len contact(s)';
} }
@override
String get publicProfilePrivacyTitle => 'Public Profile';
@override
String get publicProfilePrivacyDescription =>
'Choose who can see information about your profile.';
@override
String get publicProfileTwonlyScore => 'twonly Score';
@override
String get contactTwonlyScoreDescription =>
'The twonly Score reflects a person\'s activity on twonly. It is calculated from several factors, such as how many photos and videos they send and receive.';
@override
String get privacyVisibilityNobody => 'Nobody';
@override
String get privacyVisibilityOnlyContacts => 'Only Contacts';
@override
String get privacyVisibilityEveryone => 'Everyone';
@override @override
String get settingsNotification => 'Notification'; String get settingsNotification => 'Notification';

View file

@ -93,6 +93,13 @@
"settingsPrivacyBlockUsers": "حظر جهات الاتصال", "settingsPrivacyBlockUsers": "حظر جهات الاتصال",
"settingsPrivacyBlockUsersDesc": "لن تتمكن جهات الاتصال المحظورة من التواصل معك. يمكنك إلغاء حظر أي جهة اتصال في أي وقت.", "settingsPrivacyBlockUsersDesc": "لن تتمكن جهات الاتصال المحظورة من التواصل معك. يمكنك إلغاء حظر أي جهة اتصال في أي وقت.",
"settingsPrivacyBlockUsersCount": "جهات الاتصال: {len}", "settingsPrivacyBlockUsersCount": "جهات الاتصال: {len}",
"publicProfilePrivacyTitle": "الملف الشخصي العام",
"publicProfilePrivacyDescription": "اختر من يمكنه رؤية معلومات ملفك الشخصي.",
"publicProfileTwonlyScore": "نقاط twonly",
"contactTwonlyScoreDescription": "تعكس نقاط twonly نشاط الشخص على twonly. ويتم احتسابها استنادًا إلى عدة عوامل، مثل عدد الصور ومقاطع الفيديو التي يرسلها ويستلمها.",
"privacyVisibilityNobody": "لا أحد",
"privacyVisibilityOnlyContacts": "جهات الاتصال فقط",
"privacyVisibilityEveryone": "الجميع",
"settingsNotification": "الإشعارات", "settingsNotification": "الإشعارات",
"settingsNotifyPermission": "أذونات الإشعارات", "settingsNotifyPermission": "أذونات الإشعارات",
"settingsNotifyPermissionDesc": "افتح إعدادات النظام للسماح بالإشعارات الفورية.", "settingsNotifyPermissionDesc": "افتح إعدادات النظام للسماح بالإشعارات الفورية.",

View file

@ -102,6 +102,13 @@
"settingsPrivacyBlockUsers": "Kontakte blockieren", "settingsPrivacyBlockUsers": "Kontakte blockieren",
"settingsPrivacyBlockUsersDesc": "Blockierte Kontakte können nicht mit dir kommunizieren. Du kannst einen blockierten Kontakt jederzeit wieder entsperren.", "settingsPrivacyBlockUsersDesc": "Blockierte Kontakte können nicht mit dir kommunizieren. Du kannst einen blockierten Kontakt jederzeit wieder entsperren.",
"settingsPrivacyBlockUsersCount": "{len} Kontakt(e)", "settingsPrivacyBlockUsersCount": "{len} Kontakt(e)",
"publicProfilePrivacyTitle": "Öffentliches Profil",
"publicProfilePrivacyDescription": "Wähle aus, wer Informationen über dein Profil sehen kann.",
"publicProfileTwonlyScore": "twonly Score",
"contactTwonlyScoreDescription": "Der twonly Score spiegelt die Aktivität einer Person auf twonly wider. Er wird aus verschiedenen Faktoren berechnet, zum Beispiel daraus, wie viele Fotos und Videos sie sendet und empfängt.",
"privacyVisibilityNobody": "Niemand",
"privacyVisibilityOnlyContacts": "Nur Kontakte",
"privacyVisibilityEveryone": "Alle",
"settingsNotification": "Benachrichtigung", "settingsNotification": "Benachrichtigung",
"settingsNotifyPermission": "Benachrichtigungsberechtigung", "settingsNotifyPermission": "Benachrichtigungsberechtigung",
"settingsNotifyPermissionDesc": "Systemeinstellungen öffnen, um Push-Benachrichtigungen zu erlauben.", "settingsNotifyPermissionDesc": "Systemeinstellungen öffnen, um Push-Benachrichtigungen zu erlauben.",

View file

@ -98,6 +98,13 @@
"len": {} "len": {}
} }
}, },
"publicProfilePrivacyTitle": "Public Profile",
"publicProfilePrivacyDescription": "Choose who can see information about your profile.",
"publicProfileTwonlyScore": "twonly Score",
"contactTwonlyScoreDescription": "The twonly Score reflects a person's activity on twonly. It is calculated from several factors, such as how many photos and videos they send and receive.",
"privacyVisibilityNobody": "Nobody",
"privacyVisibilityOnlyContacts": "Only Contacts",
"privacyVisibilityEveryone": "Everyone",
"settingsNotification": "Notification", "settingsNotification": "Notification",
"settingsNotifyPermission": "Notification permissions", "settingsNotifyPermission": "Notification permissions",
"settingsNotifyPermissionDesc": "Open system settings to allow push notifications.", "settingsNotifyPermissionDesc": "Open system settings to allow push notifications.",

View file

@ -1774,6 +1774,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
passwordlessRecoveryHeartbeat, passwordlessRecoveryHeartbeat,
$core.bool? widgetSharingAllowed, $core.bool? widgetSharingAllowed,
EncryptedContent_Story? story, EncryptedContent_Story? story,
$fixnum.Int64? senderTwonlyScore,
}) { }) {
final result = create(); final result = create();
if (groupId != null) result.groupId = groupId; if (groupId != null) result.groupId = groupId;
@ -1814,6 +1815,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
if (widgetSharingAllowed != null) if (widgetSharingAllowed != null)
result.widgetSharingAllowed = widgetSharingAllowed; result.widgetSharingAllowed = widgetSharingAllowed;
if (story != null) result.story = story; if (story != null) result.story = story;
if (senderTwonlyScore != null) result.senderTwonlyScore = senderTwonlyScore;
return result; return result;
} }
@ -1893,6 +1895,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
..aOB(29, _omitFieldNames ? '' : 'widgetSharingAllowed') ..aOB(29, _omitFieldNames ? '' : 'widgetSharingAllowed')
..aOM<EncryptedContent_Story>(30, _omitFieldNames ? '' : 'story', ..aOM<EncryptedContent_Story>(30, _omitFieldNames ? '' : 'story',
subBuilder: EncryptedContent_Story.create) subBuilder: EncryptedContent_Story.create)
..aInt64(31, _omitFieldNames ? '' : 'senderTwonlyScore')
..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.')
@ -2222,6 +2225,15 @@ class EncryptedContent extends $pb.GeneratedMessage {
void clearStory() => $_clearField(30); void clearStory() => $_clearField(30);
@$pb.TagNumber(30) @$pb.TagNumber(30)
EncryptedContent_Story ensureStory() => $_ensure(26); EncryptedContent_Story ensureStory() => $_ensure(26);
@$pb.TagNumber(31)
$fixnum.Int64 get senderTwonlyScore => $_getI64(27);
@$pb.TagNumber(31)
set senderTwonlyScore($fixnum.Int64 value) => $_setInt64(27, value);
@$pb.TagNumber(31)
$core.bool hasSenderTwonlyScore() => $_has(27);
@$pb.TagNumber(31)
void clearSenderTwonlyScore() => $_clearField(31);
} }
const $core.bool _omitFieldNames = const $core.bool _omitFieldNames =

View file

@ -73,13 +73,22 @@ const EncryptedContent$json = {
'10': 'widgetSharingAllowed', '10': 'widgetSharingAllowed',
'17': true '17': true
}, },
{
'1': 'sender_twonly_score',
'3': 31,
'4': 1,
'5': 3,
'9': 6,
'10': 'senderTwonlyScore',
'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': 6, '9': 7,
'10': 'messageUpdate', '10': 'messageUpdate',
'17': true '17': true
}, },
@ -89,7 +98,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.Media', '6': '.EncryptedContent.Media',
'9': 7, '9': 8,
'10': 'media', '10': 'media',
'17': true '17': true
}, },
@ -99,7 +108,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.MediaUpdate', '6': '.EncryptedContent.MediaUpdate',
'9': 8, '9': 9,
'10': 'mediaUpdate', '10': 'mediaUpdate',
'17': true '17': true
}, },
@ -109,7 +118,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ContactUpdate', '6': '.EncryptedContent.ContactUpdate',
'9': 9, '9': 10,
'10': 'contactUpdate', '10': 'contactUpdate',
'17': true '17': true
}, },
@ -119,7 +128,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ContactRequest', '6': '.EncryptedContent.ContactRequest',
'9': 10, '9': 11,
'10': 'contactRequest', '10': 'contactRequest',
'17': true '17': true
}, },
@ -129,7 +138,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.FlameSync', '6': '.EncryptedContent.FlameSync',
'9': 11, '9': 12,
'10': 'flameSync', '10': 'flameSync',
'17': true '17': true
}, },
@ -139,7 +148,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.Reaction', '6': '.EncryptedContent.Reaction',
'9': 12, '9': 13,
'10': 'reaction', '10': 'reaction',
'17': true '17': true
}, },
@ -149,7 +158,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.TextMessage', '6': '.EncryptedContent.TextMessage',
'9': 13, '9': 14,
'10': 'textMessage', '10': 'textMessage',
'17': true '17': true
}, },
@ -159,7 +168,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.GroupCreate', '6': '.EncryptedContent.GroupCreate',
'9': 14, '9': 15,
'10': 'groupCreate', '10': 'groupCreate',
'17': true '17': true
}, },
@ -169,7 +178,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.GroupJoin', '6': '.EncryptedContent.GroupJoin',
'9': 15, '9': 16,
'10': 'groupJoin', '10': 'groupJoin',
'17': true '17': true
}, },
@ -179,7 +188,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.GroupUpdate', '6': '.EncryptedContent.GroupUpdate',
'9': 16, '9': 17,
'10': 'groupUpdate', '10': 'groupUpdate',
'17': true '17': true
}, },
@ -189,7 +198,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ResendGroupPublicKey', '6': '.EncryptedContent.ResendGroupPublicKey',
'9': 17, '9': 18,
'10': 'resendGroupPublicKey', '10': 'resendGroupPublicKey',
'17': true '17': true
}, },
@ -199,7 +208,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.ErrorMessages', '6': '.EncryptedContent.ErrorMessages',
'9': 18, '9': 19,
'10': 'errorMessages', '10': 'errorMessages',
'17': true '17': true
}, },
@ -209,7 +218,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.AdditionalDataMessage', '6': '.EncryptedContent.AdditionalDataMessage',
'9': 19, '9': 20,
'10': 'additionalDataMessage', '10': 'additionalDataMessage',
'17': true '17': true
}, },
@ -219,7 +228,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.TypingIndicator', '6': '.EncryptedContent.TypingIndicator',
'9': 20, '9': 21,
'10': 'typingIndicator', '10': 'typingIndicator',
'17': true '17': true
}, },
@ -229,7 +238,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.UserDiscoveryRequest', '6': '.EncryptedContent.UserDiscoveryRequest',
'9': 21, '9': 22,
'10': 'userDiscoveryRequest', '10': 'userDiscoveryRequest',
'17': true '17': true
}, },
@ -239,7 +248,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.UserDiscoveryUpdate', '6': '.EncryptedContent.UserDiscoveryUpdate',
'9': 22, '9': 23,
'10': 'userDiscoveryUpdate', '10': 'userDiscoveryUpdate',
'17': true '17': true
}, },
@ -249,7 +258,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.KeyVerificationProof', '6': '.EncryptedContent.KeyVerificationProof',
'9': 23, '9': 24,
'10': 'keyVerificationProof', '10': 'keyVerificationProof',
'17': true '17': true
}, },
@ -259,7 +268,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.PasswordLessRecovery', '6': '.EncryptedContent.PasswordLessRecovery',
'9': 24, '9': 25,
'10': 'passwordlessRecovery', '10': 'passwordlessRecovery',
'17': true '17': true
}, },
@ -269,7 +278,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.PasswordLessRecoveryHeartbeat', '6': '.EncryptedContent.PasswordLessRecoveryHeartbeat',
'9': 25, '9': 26,
'10': 'passwordlessRecoveryHeartbeat', '10': 'passwordlessRecoveryHeartbeat',
'17': true '17': true
}, },
@ -279,7 +288,7 @@ const EncryptedContent$json = {
'4': 1, '4': 1,
'5': 11, '5': 11,
'6': '.EncryptedContent.Story', '6': '.EncryptedContent.Story',
'9': 26, '9': 27,
'10': 'story', '10': 'story',
'17': true '17': true
}, },
@ -314,6 +323,7 @@ const EncryptedContent$json = {
{'1': '_sender_user_discovery_version'}, {'1': '_sender_user_discovery_version'},
{'1': '_ask_for_friend_promotions'}, {'1': '_ask_for_friend_promotions'},
{'1': '_widget_sharing_allowed'}, {'1': '_widget_sharing_allowed'},
{'1': '_sender_twonly_score'},
{'1': '_message_update'}, {'1': '_message_update'},
{'1': '_media'}, {'1': '_media'},
{'1': '_media_update'}, {'1': '_media_update'},
@ -894,115 +904,116 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'JfZGlzY292ZXJ5X3ZlcnNpb24YFSABKAxIA1Iac2VuZGVyVXNlckRpc2NvdmVyeVZlcnNpb26I' 'JfZGlzY292ZXJ5X3ZlcnNpb24YFSABKAxIA1Iac2VuZGVyVXNlckRpc2NvdmVyeVZlcnNpb26I'
'AQESPgoZYXNrX2Zvcl9mcmllbmRfcHJvbW90aW9ucxgZIAEoCEgEUhZhc2tGb3JGcmllbmRQcm' 'AQESPgoZYXNrX2Zvcl9mcmllbmRfcHJvbW90aW9ucxgZIAEoCEgEUhZhc2tGb3JGcmllbmRQcm'
'9tb3Rpb25ziAEBEjkKFndpZGdldF9zaGFyaW5nX2FsbG93ZWQYHSABKAhIBVIUd2lkZ2V0U2hh' '9tb3Rpb25ziAEBEjkKFndpZGdldF9zaGFyaW5nX2FsbG93ZWQYHSABKAhIBVIUd2lkZ2V0U2hh'
'cmluZ0FsbG93ZWSIAQESSwoObWVzc2FnZV91cGRhdGUYBSABKAsyHy5FbmNyeXB0ZWRDb250ZW' 'cmluZ0FsbG93ZWSIAQESMwoTc2VuZGVyX3R3b25seV9zY29yZRgfIAEoA0gGUhFzZW5kZXJUd2'
'50Lk1lc3NhZ2VVcGRhdGVIBlINbWVzc2FnZVVwZGF0ZYgBARIyCgVtZWRpYRgGIAEoCzIXLkVu' '9ubHlTY29yZYgBARJLCg5tZXNzYWdlX3VwZGF0ZRgFIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQu'
'Y3J5cHRlZENvbnRlbnQuTWVkaWFIB1IFbWVkaWGIAQESRQoMbWVkaWFfdXBkYXRlGAcgASgLMh' 'TWVzc2FnZVVwZGF0ZUgHUg1tZXNzYWdlVXBkYXRliAEBEjIKBW1lZGlhGAYgASgLMhcuRW5jcn'
'0uRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZUgIUgttZWRpYVVwZGF0ZYgBARJLCg5jb250' 'lwdGVkQ29udGVudC5NZWRpYUgIUgVtZWRpYYgBARJFCgxtZWRpYV91cGRhdGUYByABKAsyHS5F'
'YWN0X3VwZGF0ZRgIIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZUgJUg1jb2' 'bmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlSAlSC21lZGlhVXBkYXRliAEBEksKDmNvbnRhY3'
'50YWN0VXBkYXRliAEBEk4KD2NvbnRhY3RfcmVxdWVzdBgJIAEoCzIgLkVuY3J5cHRlZENvbnRl' 'RfdXBkYXRlGAggASgLMh8uRW5jcnlwdGVkQ29udGVudC5Db250YWN0VXBkYXRlSApSDWNvbnRh'
'bnQuQ29udGFjdFJlcXVlc3RIClIOY29udGFjdFJlcXVlc3SIAQESPwoKZmxhbWVfc3luYxgKIA' 'Y3RVcGRhdGWIAQESTgoPY29udGFjdF9yZXF1ZXN0GAkgASgLMiAuRW5jcnlwdGVkQ29udGVudC'
'EoCzIbLkVuY3J5cHRlZENvbnRlbnQuRmxhbWVTeW5jSAtSCWZsYW1lU3luY4gBARI7CghyZWFj' '5Db250YWN0UmVxdWVzdEgLUg5jb250YWN0UmVxdWVzdIgBARI/CgpmbGFtZV9zeW5jGAogASgL'
'dGlvbhgMIAEoCzIaLkVuY3J5cHRlZENvbnRlbnQuUmVhY3Rpb25IDFIIcmVhY3Rpb26IAQESRQ' 'MhsuRW5jcnlwdGVkQ29udGVudC5GbGFtZVN5bmNIDFIJZmxhbWVTeW5jiAEBEjsKCHJlYWN0aW'
'oMdGV4dF9tZXNzYWdlGA0gASgLMh0uRW5jcnlwdGVkQ29udGVudC5UZXh0TWVzc2FnZUgNUgt0' '9uGAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgNUghyZWFjdGlvbogBARJFCgx0'
'ZXh0TWVzc2FnZYgBARJFCgxncm91cF9jcmVhdGUYDiABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lk' 'ZXh0X21lc3NhZ2UYDSABKAsyHS5FbmNyeXB0ZWRDb250ZW50LlRleHRNZXNzYWdlSA5SC3RleH'
'dyb3VwQ3JlYXRlSA5SC2dyb3VwQ3JlYXRliAEBEj8KCmdyb3VwX2pvaW4YDyABKAsyGy5FbmNy' 'RNZXNzYWdliAEBEkUKDGdyb3VwX2NyZWF0ZRgOIAEoCzIdLkVuY3J5cHRlZENvbnRlbnQuR3Jv'
'eXB0ZWRDb250ZW50Lkdyb3VwSm9pbkgPUglncm91cEpvaW6IAQESRQoMZ3JvdXBfdXBkYXRlGB' 'dXBDcmVhdGVID1ILZ3JvdXBDcmVhdGWIAQESPwoKZ3JvdXBfam9pbhgPIAEoCzIbLkVuY3J5cH'
'AgASgLMh0uRW5jcnlwdGVkQ29udGVudC5Hcm91cFVwZGF0ZUgQUgtncm91cFVwZGF0ZYgBARJi' 'RlZENvbnRlbnQuR3JvdXBKb2luSBBSCWdyb3VwSm9pbogBARJFCgxncm91cF91cGRhdGUYECAB'
'ChdyZXNlbmRfZ3JvdXBfcHVibGljX2tleRgRIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuUmVzZW' 'KAsyHS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSBFSC2dyb3VwVXBkYXRliAEBEmIKF3'
'5kR3JvdXBQdWJsaWNLZXlIEVIUcmVzZW5kR3JvdXBQdWJsaWNLZXmIAQESSwoOZXJyb3JfbWVz' 'Jlc2VuZF9ncm91cF9wdWJsaWNfa2V5GBEgASgLMiYuRW5jcnlwdGVkQ29udGVudC5SZXNlbmRH'
'c2FnZXMYEiABKAsyHy5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZXNIElINZXJyb3JNZX' 'cm91cFB1YmxpY0tleUgSUhRyZXNlbmRHcm91cFB1YmxpY0tleYgBARJLCg5lcnJvcl9tZXNzYW'
'NzYWdlc4gBARJkChdhZGRpdGlvbmFsX2RhdGFfbWVzc2FnZRgTIAEoCzInLkVuY3J5cHRlZENv' 'dlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlc0gTUg1lcnJvck1lc3Nh'
'bnRlbnQuQWRkaXRpb25hbERhdGFNZXNzYWdlSBNSFWFkZGl0aW9uYWxEYXRhTWVzc2FnZYgBAR' 'Z2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgASgLMicuRW5jcnlwdGVkQ29udG'
'JRChB0eXBpbmdfaW5kaWNhdG9yGBQgASgLMiEuRW5jcnlwdGVkQ29udGVudC5UeXBpbmdJbmRp' 'VudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIFFIVYWRkaXRpb25hbERhdGFNZXNzYWdliAEBElEK'
'Y2F0b3JIFFIPdHlwaW5nSW5kaWNhdG9yiAEBEmEKFnVzZXJfZGlzY292ZXJ5X3JlcXVlc3QYFi' 'EHR5cGluZ19pbmRpY2F0b3IYFCABKAsyIS5FbmNyeXB0ZWRDb250ZW50LlR5cGluZ0luZGljYX'
'ABKAsyJi5FbmNyeXB0ZWRDb250ZW50LlVzZXJEaXNjb3ZlcnlSZXF1ZXN0SBVSFHVzZXJEaXNj' 'RvckgVUg90eXBpbmdJbmRpY2F0b3KIAQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEo'
'b3ZlcnlSZXF1ZXN0iAEBEl4KFXVzZXJfZGlzY292ZXJ5X3VwZGF0ZRgXIAEoCzIlLkVuY3J5cH' 'CzImLkVuY3J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVJlcXVlc3RIFlIUdXNlckRpc2Nvdm'
'RlZENvbnRlbnQuVXNlckRpc2NvdmVyeVVwZGF0ZUgWUhN1c2VyRGlzY292ZXJ5VXBkYXRliAEB' 'VyeVJlcXVlc3SIAQESXgoVdXNlcl9kaXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVk'
'EmEKFmtleV92ZXJpZmljYXRpb25fcHJvb2YYGCABKAsyJi5FbmNyeXB0ZWRDb250ZW50LktleV' 'Q29udGVudC5Vc2VyRGlzY292ZXJ5VXBkYXRlSBdSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQESYQ'
'ZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBEmAKFXBhc3N3b3Jk' 'oWa2V5X3ZlcmlmaWNhdGlvbl9wcm9vZhgYIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuS2V5VmVy'
'bGVzc19yZWNvdmVyeRgaIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuUGFzc3dvcmRMZXNzUmVjb3' 'aWZpY2F0aW9uUHJvb2ZIGFIUa2V5VmVyaWZpY2F0aW9uUHJvb2aIAQESYAoVcGFzc3dvcmRsZX'
'ZlcnlIGFIUcGFzc3dvcmRsZXNzUmVjb3ZlcnmIAQESfAofcGFzc3dvcmRsZXNzX3JlY292ZXJ5' 'NzX3JlY292ZXJ5GBogASgLMiYuRW5jcnlwdGVkQ29udGVudC5QYXNzd29yZExlc3NSZWNvdmVy'
'X2hlYXJ0YmVhdBgbIAEoCzIvLkVuY3J5cHRlZENvbnRlbnQuUGFzc3dvcmRMZXNzUmVjb3Zlcn' 'eUgZUhRwYXNzd29yZGxlc3NSZWNvdmVyeYgBARJ8Ch9wYXNzd29yZGxlc3NfcmVjb3ZlcnlfaG'
'lIZWFydGJlYXRIGVIdcGFzc3dvcmRsZXNzUmVjb3ZlcnlIZWFydGJlYXSIAQESMgoFc3RvcnkY' 'VhcnRiZWF0GBsgASgLMi8uRW5jcnlwdGVkQ29udGVudC5QYXNzd29yZExlc3NSZWNvdmVyeUhl'
'HiABKAsyFy5FbmNyeXB0ZWRDb250ZW50LlN0b3J5SBpSBXN0b3J5iAEBGpYCCg1FcnJvck1lc3' 'YXJ0YmVhdEgaUh1wYXNzd29yZGxlc3NSZWNvdmVyeUhlYXJ0YmVhdIgBARIyCgVzdG9yeRgeIA'
'NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlw' 'EoCzIXLkVuY3J5cHRlZENvbnRlbnQuU3RvcnlIG1IFc3RvcnmIAQEalgIKDUVycm9yTWVzc2Fn'
'ZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRfaWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQinA' 'ZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeXBlUg'
'EKBFR5cGUSPAo4RVJST1JfUFJPQ0VTU0lOR19NRVNTQUdFX0NSRUFURURfQUNDT1VOVF9SRVFV' 'R0eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCKcAQoE'
'RVNUX0lOU1RFQUQQABIYChRVTktOT1dOX01FU1NBR0VfVFlQRRACEhcKE1NFU1NJT05fT1VUX0' 'VHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1'
'9GX1NZTkMQAxIjCh9HUk9VUF9OT1RfRk9VTkRfT1JfTk9UX0FfTUVNQkVSEAQahwEKC0dyb3Vw' 'RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVTU0FHRV9UWVBFEAISFwoTU0VTU0lPTl9PVVRfT0Zf'
'Q3JlYXRlEhsKCXN0YXRlX2tleRgDIAEoDFIIc3RhdGVLZXkSKAoQZ3JvdXBfcHVibGljX2tleR' 'U1lOQxADEiMKH0dST1VQX05PVF9GT1VORF9PUl9OT1RfQV9NRU1CRVIQBBqHAQoLR3JvdXBDcm'
'gEIAEoDFIOZ3JvdXBQdWJsaWNLZXkSIgoKZ3JvdXBfbmFtZRgFIAEoCUgAUglncm91cE5hbWWI' 'VhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91cF9wdWJsaWNfa2V5GAQg'
'AQFCDQoLX2dyb3VwX25hbWUaNQoJR3JvdXBKb2luEigKEGdyb3VwX3B1YmxpY19rZXkYASABKA' 'ASgMUg5ncm91cFB1YmxpY0tleRIiCgpncm91cF9uYW1lGAUgASgJSABSCWdyb3VwTmFtZYgBAU'
'xSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibGljS2V5GsgCCgtHcm91cFVwZGF0' 'INCgtfZ3JvdXBfbmFtZRo1CglHcm91cEpvaW4SKAoQZ3JvdXBfcHVibGljX2tleRgBIAEoDFIO'
'ZRIqChFncm91cF9hY3Rpb25fdHlwZRgBIAEoCVIPZ3JvdXBBY3Rpb25UeXBlEjMKE2FmZmVjdG' 'Z3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkayAIKC0dyb3VwVXBkYXRlEi'
'VkX2NvbnRhY3RfaWQYAiABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESKQoObmV3X2dyb3Vw' 'oKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMwoTYWZmZWN0ZWRf'
'X25hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElcKJm5ld19kZWxldGVfbWVzc2FnZXNfYW' 'Y29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZIgBARIpCg5uZXdfZ3JvdXBfbm'
'Z0ZXJfbWlsbGlzZWNvbmRzGAQgASgDSAJSIm5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNl' 'FtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2RlbGV0ZV9tZXNzYWdlc19hZnRl'
'Y29uZHOIAQFCFgoUX2FmZmVjdGVkX2NvbnRhY3RfaWRCEQoPX25ld19ncm91cF9uYW1lQikKJ1' 'cl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb2'
'9uZXdfZGVsZXRlX21lc3NhZ2VzX2FmdGVyX21pbGxpc2Vjb25kcxqIAgoLVGV4dE1lc3NhZ2US' '5kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbmV3X2dyb3VwX25hbWVCKQonX25l'
'KgoRc2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgAS' 'd19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGogCCgtUZXh0TWVzc2FnZRIqCh'
'gJUgR0ZXh0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2Vf' 'FzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlS'
'aWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQESOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdG' 'BHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZB'
'EYBSABKAxIAVIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBQhMKEV9xdW90ZV9tZXNzYWdlX2lk' 'gEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgF'
'QhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRrmAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEi' 'IAEoDEgBUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQFCEwoRX3F1b3RlX21lc3NhZ2VfaWRCGg'
'oKEXNlbmRlcl9tZXNzYWdlX2lkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1w' 'oYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGuYBChVBZGRpdGlvbmFsRGF0YU1lc3NhZ2USKgoR'
'GAIgASgDUgl0aW1lc3RhbXASEgoEdHlwZRgDIAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3' 'c2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIcCgl0aW1lc3RhbXAYAi'
'NhZ2VfZGF0YRgEIAEoDEgAUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQESFgoGaGlkZGVuGAUg' 'ABKANSCXRpbWVzdGFtcBISCgR0eXBlGAMgASgJUgR0eXBlEjsKF2FkZGl0aW9uYWxfbWVzc2Fn'
'ASgIUgZoaWRkZW5CGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGmQKCFJlYWN0aW9uEioKEX' 'ZV9kYXRhGAQgASgMSABSFWFkZGl0aW9uYWxNZXNzYWdlRGF0YYgBARIWCgZoaWRkZW4YBSABKA'
'RhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSFAoFZW1vamkYAiABKAlS' 'hSBmhpZGRlbkIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEaZAoIUmVhY3Rpb24SKgoRdGFy'
'BWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGr4CCg1NZXNzYWdlVXBkYXRlEjgKBHR5cG' 'Z2V0X21lc3NhZ2VfaWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW'
'UYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUuVHlwZVIEdHlwZRIvChFz' '1vamkSFgoGcmVtb3ZlGAMgASgIUgZyZW1vdmUavgIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgB'
'ZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESPQobbXVsdGlwbG' 'IAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi8KEXNlbm'
'VfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoE' 'Rlcl9tZXNzYWdlX2lkGAIgASgJSABSD3NlbmRlck1lc3NhZ2VJZIgBARI9ChttdWx0aXBsZV90'
'dGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBF' 'YXJnZXRfbWVzc2FnZV9pZHMYAyADKAlSGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxIXCgR0ZX'
'R5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCFAoSX3NlbmRlcl9t' 'h0GAQgASgJSAFSBHRleHSIAQESHAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlw'
'ZXNzYWdlX2lkQgcKBV90ZXh0GrsGCgVNZWRpYRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCV' 'ZRIKCgZERUxFVEUQABINCglFRElUX1RFWFQQARIKCgZPUEVORUQQAkIUChJfc2VuZGVyX21lc3'
'IPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk1lZGlh' 'NhZ2VfaWRCBwoFX3RleHQauwYKBU1lZGlhEioKEXNlbmRlcl9tZXNzYWdlX2lkGAEgASgJUg9z'
'LlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY29uZHMYAyABKANIAFIaZG' 'ZW5kZXJNZXNzYWdlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVH'
'lzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNwoXcmVxdWlyZXNfYXV0aGVudGljYXRpb24Y' 'lwZVIEdHlwZRJGCh1kaXNwbGF5X2xpbWl0X2luX21pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNw'
'BCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3' 'bGF5TGltaXRJbk1pbGxpc2Vjb25kc4gBARI3ChdyZXF1aXJlc19hdXRoZW50aWNhdGlvbhgEIA'
'RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIqCg5k' 'EoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVzdGFt'
'b3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEioKDmVuY3J5cHRpb25fa2' 'cBItChBxdW90ZV9tZXNzYWdlX2lkGAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEioKDmRvd2'
'V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQESKgoOZW5jcnlwdGlvbl9tYWMYCSABKAxIBFIN' '5sb2FkX3Rva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKgoOZW5jcnlwdGlvbl9rZXkY'
'ZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW9uX25vbmNlGAogASgMSAVSD2VuY3J5cHRpb2' 'CCABKAxIA1INZW5jcnlwdGlvbktleYgBARIqCg5lbmNyeXB0aW9uX21hYxgJIAEoDEgEUg1lbm'
'5Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgLIAEoDEgGUhVhZGRpdGlvbmFs' 'NyeXB0aW9uTWFjiAEBEi4KEGVuY3J5cHRpb25fbm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5v'
'TWVzc2FnZURhdGGIAQESJAoLd2lkZ2V0X29ubHkYDCABKAhIB1IKd2lkZ2V0T25seYgBASI+Cg' 'bmNliAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZX'
'RUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxACEgcKA0dJRhADEgkKBUFV' 'NzYWdlRGF0YYgBARIkCgt3aWRnZXRfb25seRgMIAEoCEgHUgp3aWRnZXRPbmx5iAEBIj4KBFR5'
'RElPEARCIAoeX2Rpc3BsYXlfbGltaXRfaW5fbWlsbGlzZWNvbmRzQhMKEV9xdW90ZV9tZXNzYW' 'cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU'
'dlX2lkQhEKD19kb3dubG9hZF90b2tlbkIRCg9fZW5jcnlwdGlvbl9rZXlCEQoPX2VuY3J5cHRp' '8QBEIgCh5fZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY29uZHNCEwoRX3F1b3RlX21lc3NhZ2Vf'
'b25fbWFjQhMKEV9lbmNyeXB0aW9uX25vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YU' 'aWRCEQoPX2Rvd25sb2FkX3Rva2VuQhEKD19lbmNyeXB0aW9uX2tleUIRCg9fZW5jcnlwdGlvbl'
'IOCgxfd2lkZ2V0X29ubHkaTgoFU3RvcnkSLQoFbWVkaWEYASABKAsyFy5FbmNyeXB0ZWRDb250' '9tYWNCEwoRX2VuY3J5cHRpb25fbm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhQg4K'
'ZW50Lk1lZGlhUgVtZWRpYRIWCgZub3RpZnkYAiABKAhSBm5vdGlmeRqpAQoLTWVkaWFVcGRhdG' 'DF93aWRnZXRfb25seRpOCgVTdG9yeRItCgVtZWRpYRgBIAEoCzIXLkVuY3J5cHRlZENvbnRlbn'
'USNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGUuVHlwZVIEdHlw' 'QuTWVkaWFSBW1lZGlhEhYKBm5vdGlmeRgCIAEoCFIGbm90aWZ5GqkBCgtNZWRpYVVwZGF0ZRI2'
'ZRIqChF0YXJnZXRfbWVzc2FnZV9pZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDA' 'CgR0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEi'
'oIUkVPUEVORUQQABIKCgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFj' 'oKEXRhcmdldF9tZXNzYWdlX2lkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghS'
'dFJlcXVlc3QSOQoEdHlwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3' 'RU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0Um'
'QuVHlwZVIEdHlwZSIrCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQ' 'VxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5U'
'AhqkAgoNQ29udGFjdFVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db2' 'eXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGq'
'50YWN0VXBkYXRlLlR5cGVSBHR5cGUSNwoVYXZhdGFyX3N2Z19jb21wcmVzc2VkGAIgASgMSABS' 'QCCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRh'
'E2F2YXRhclN2Z0NvbXByZXNzZWSIAQESHwoIdXNlcm5hbWUYAyABKAlIAVIIdXNlcm5hbWWIAQ' 'Y3RVcGRhdGUuVHlwZVIEdHlwZRI3ChVhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAFITYX'
'ESJgoMZGlzcGxheV9uYW1lGAQgASgJSAJSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoHUkVR' 'ZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgBARIm'
'VUVTVBAAEgoKBlVQREFURRABQhgKFl9hdmF0YXJfc3ZnX2NvbXByZXNzZWRCCwoJX3VzZXJuYW' 'CgxkaXNwbGF5X25hbWUYBCABKAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRV'
'1lQg8KDV9kaXNwbGF5X25hbWUarwEKCUZsYW1lU3luYxIjCg1mbGFtZV9jb3VudGVyGAEgASgD' 'NUEAASCgoGVVBEQVRFEAFCGAoWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5hbWVC'
'UgxmbGFtZUNvdW50ZXISOQoZbGFzdF9mbGFtZV9jb3VudGVyX2NoYW5nZRgCIAEoA1IWbGFzdE' 'DwoNX2Rpc3BsYXlfbmFtZRqvAQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDG'
'ZsYW1lQ291bnRlckNoYW5nZRIfCgtiZXN0X2ZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZBIhCgxm' 'ZsYW1lQ291bnRlchI5ChlsYXN0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0Rmxh'
'b3JjZV91cGRhdGUYBCABKAhSC2ZvcmNlVXBkYXRlGk0KD1R5cGluZ0luZGljYXRvchIbCglpc1' 'bWVDb3VudGVyQ2hhbmdlEh8KC2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcm'
'90eXBpbmcYASABKAhSCGlzVHlwaW5nEh0KCmNyZWF0ZWRfYXQYAiABKANSCWNyZWF0ZWRBdBo/' 'NlX3VwZGF0ZRgEIAEoCFILZm9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5'
'ChRVc2VyRGlzY292ZXJ5UmVxdWVzdBInCg9jdXJyZW50X3ZlcnNpb24YASABKAxSDmN1cnJlbn' 'cGluZxgBIAEoCFIIaXNUeXBpbmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFF'
'RWZXJzaW9uGjEKE1VzZXJEaXNjb3ZlcnlVcGRhdGUSGgoIbWVzc2FnZXMYASADKAxSCG1lc3Nh' 'VzZXJEaXNjb3ZlcnlSZXF1ZXN0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZl'
'Z2VzGj0KFEtleVZlcmlmaWNhdGlvblByb29mEiUKDmNhbGN1bGF0ZWRfbWFjGAEgASgMUg1jYW' 'cnNpb24aMQoTVXNlckRpc2NvdmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZX'
'xjdWxhdGVkTWFjGpsBChRQYXNzd29yZExlc3NSZWNvdmVyeRI1ChNyZWNvdmVyeVNlY3JldFNo' 'MaPQoUS2V5VmVyaWZpY2F0aW9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1'
'YXJlGAEgASgMSABSE3JlY292ZXJ5U2VjcmV0U2hhcmWIAQESFgoGZGVsZXRlGAIgASgIUgZkZW' 'bGF0ZWRNYWMamwEKFFBhc3N3b3JkTGVzc1JlY292ZXJ5EjUKE3JlY292ZXJ5U2VjcmV0U2hhcm'
'xldGUSHAoJdGhyZXNob2xkGAMgASgDUgl0aHJlc2hvbGRCFgoUX3JlY292ZXJ5U2VjcmV0U2hh' 'UYASABKAxIAFITcmVjb3ZlcnlTZWNyZXRTaGFyZYgBARIWCgZkZWxldGUYAiABKAhSBmRlbGV0'
'cmUaMwodUGFzc3dvcmRMZXNzUmVjb3ZlcnlIZWFydGJlYXQSEgoEaGFzaBgBIAEoDFIEaGFzaE' 'ZRIcCgl0aHJlc2hvbGQYAyABKANSCXRocmVzaG9sZEIWChRfcmVjb3ZlcnlTZWNyZXRTaGFyZR'
'ILCglfZ3JvdXBfaWRCEQoPX2lzX2RpcmVjdF9jaGF0QhkKF19zZW5kZXJfcHJvZmlsZV9jb3Vu' 'ozCh1QYXNzd29yZExlc3NSZWNvdmVyeUhlYXJ0YmVhdBISCgRoYXNoGAEgASgMUgRoYXNoQgsK'
'dGVyQiAKHl9zZW5kZXJfdXNlcl9kaXNjb3ZlcnlfdmVyc2lvbkIcChpfYXNrX2Zvcl9mcmllbm' 'CV9ncm91cF9pZEIRCg9faXNfZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm9maWxlX2NvdW50ZX'
'RfcHJvbW90aW9uc0IZChdfd2lkZ2V0X3NoYXJpbmdfYWxsb3dlZEIRCg9fbWVzc2FnZV91cGRh' 'JCIAoeX3NlbmRlcl91c2VyX2Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tfZm9yX2ZyaWVuZF9w'
'dGVCCAoGX21lZGlhQg8KDV9tZWRpYV91cGRhdGVCEQoPX2NvbnRhY3RfdXBkYXRlQhIKEF9jb2' 'cm9tb3Rpb25zQhkKF193aWRnZXRfc2hhcmluZ19hbGxvd2VkQhYKFF9zZW5kZXJfdHdvbmx5X3'
'50YWN0X3JlcXVlc3RCDQoLX2ZsYW1lX3N5bmNCCwoJX3JlYWN0aW9uQg8KDV90ZXh0X21lc3Nh' 'Njb3JlQhEKD19tZXNzYWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX21lZGlhX3VwZGF0ZUIRCg9f'
'Z2VCDwoNX2dyb3VwX2NyZWF0ZUINCgtfZ3JvdXBfam9pbkIPCg1fZ3JvdXBfdXBkYXRlQhoKGF' 'Y29udGFjdF91cGRhdGVCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtfZmxhbWVfc3luY0ILCglfcm'
'9yZXNlbmRfZ3JvdXBfcHVibGljX2tleUIRCg9fZXJyb3JfbWVzc2FnZXNCGgoYX2FkZGl0aW9u' 'VhY3Rpb25CDwoNX3RleHRfbWVzc2FnZUIPCg1fZ3JvdXBfY3JlYXRlQg0KC19ncm91cF9qb2lu'
'YWxfZGF0YV9tZXNzYWdlQhMKEV90eXBpbmdfaW5kaWNhdG9yQhkKF191c2VyX2Rpc2NvdmVyeV' 'Qg8KDV9ncm91cF91cGRhdGVCGgoYX3Jlc2VuZF9ncm91cF9wdWJsaWNfa2V5QhEKD19lcnJvcl'
'9yZXF1ZXN0QhgKFl91c2VyX2Rpc2NvdmVyeV91cGRhdGVCGQoXX2tleV92ZXJpZmljYXRpb25f' '9tZXNzYWdlc0IaChhfYWRkaXRpb25hbF9kYXRhX21lc3NhZ2VCEwoRX3R5cGluZ19pbmRpY2F0'
'cHJvb2ZCGAoWX3Bhc3N3b3JkbGVzc19yZWNvdmVyeUIiCiBfcGFzc3dvcmRsZXNzX3JlY292ZX' 'b3JCGQoXX3VzZXJfZGlzY292ZXJ5X3JlcXVlc3RCGAoWX3VzZXJfZGlzY292ZXJ5X3VwZGF0ZU'
'J5X2hlYXJ0YmVhdEIICgZfc3RvcnlKBAgcEB0='); 'IZChdfa2V5X3ZlcmlmaWNhdGlvbl9wcm9vZkIYChZfcGFzc3dvcmRsZXNzX3JlY292ZXJ5QiIK'
'IF9wYXNzd29yZGxlc3NfcmVjb3ZlcnlfaGVhcnRiZWF0QggKBl9zdG9yeUoECBwQHQ==');

View file

@ -42,6 +42,7 @@ import 'package:twonly/src/visual/views/settings/help/news.view.dart';
import 'package:twonly/src/visual/views/settings/notification.view.dart'; import 'package:twonly/src/visual/views/settings/notification.view.dart';
import 'package:twonly/src/visual/views/settings/privacy.view.dart'; import 'package:twonly/src/visual/views/settings/privacy.view.dart';
import 'package:twonly/src/visual/views/settings/privacy/block_users.view.dart'; import 'package:twonly/src/visual/views/settings/privacy/block_users.view.dart';
import 'package:twonly/src/visual/views/settings/privacy/public_profile.view.dart';
import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart'; import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart';
import 'package:twonly/src/visual/views/settings/profile/modify_avatar.view.dart'; import 'package:twonly/src/visual/views/settings/profile/modify_avatar.view.dart';
import 'package:twonly/src/visual/views/settings/profile/profile.view.dart'; import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
@ -241,6 +242,10 @@ final routerProvider = GoRouter(
path: 'user_discovery', path: 'user_discovery',
builder: (context, state) => const UserDiscoverySettingsView(), builder: (context, state) => const UserDiscoverySettingsView(),
), ),
GoRoute(
path: 'public_profile',
builder: (context, state) => const PublicProfilePrivacyView(),
),
], ],
), ),
GoRoute( GoRoute(

View file

@ -19,6 +19,7 @@ import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart
import 'package:twonly/src/visual/components/snackbar.dart'; import 'package:twonly/src/visual/components/snackbar.dart';
import 'package:twonly/src/visual/components/verification_badge.comp.dart'; import 'package:twonly/src/visual/components/verification_badge.comp.dart';
import 'package:twonly/src/visual/elements/better_list_title.element.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart';
import 'package:twonly/src/visual/elements/my_chip.element.dart';
import 'package:twonly/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart'; import 'package:twonly/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.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';
import 'package:twonly/src/visual/views/contact/contact_components/verification_expansion_tile.comp.dart'; import 'package:twonly/src/visual/views/contact/contact_components/verification_expansion_tile.comp.dart';
@ -225,6 +226,39 @@ class _ContactViewState extends State<ContactView> {
), ),
if (getContactDisplayName(contact) != contact.username) if (getContactDisplayName(contact) != contact.username)
Center(child: Text('(${contact.username})')), Center(child: Text('(${contact.username})')),
if (contact.twonlyScore != null)
Padding(
padding: const EdgeInsets.only(top: 10),
child: Center(
child: IntrinsicWidth(
child: MyChip(
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
const FaIcon(FontAwesomeIcons.trophy, size: 14),
const SizedBox(width: 7),
Text('${contact.twonlyScore}'),
],
),
onTap: () => showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: Text(context.lang.publicProfileTwonlyScore),
content: Text(
context.lang.contactTwonlyScoreDescription,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.lang.ok),
),
],
),
),
),
),
),
),
const SizedBox(height: 50), const SizedBox(height: 50),
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.solidComments, icon: FontAwesomeIcons.solidComments,

View file

@ -47,6 +47,10 @@ class _PrivacyViewState extends State<PrivacyView> {
), ),
body: ListView( body: ListView(
children: [ children: [
ListTile(
title: Text(context.lang.publicProfilePrivacyTitle),
onTap: () => context.push(Routes.settingsPrivacyPublicProfile),
),
ListTile( ListTile(
title: Text(context.lang.settingsPrivacyBlockUsers), title: Text(context.lang.settingsPrivacyBlockUsers),
subtitle: StreamBuilder( subtitle: StreamBuilder(

View file

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:twonly/core/user_config.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/misc.dart';
class PublicProfilePrivacyView extends StatefulWidget {
const PublicProfilePrivacyView({super.key});
@override
State<PublicProfilePrivacyView> createState() =>
_PublicProfilePrivacyViewState();
}
class _PublicProfilePrivacyViewState extends State<PublicProfilePrivacyView> {
Future<void> _setTwonlyScoreVisibility(
TwonlyScoreVisibility visibility,
) async {
await UserService.update((user) {
user.twonlyScoreVisibility = visibility;
});
if (mounted) setState(() {});
}
String _visibilityLabel(TwonlyScoreVisibility visibility) {
return switch (visibility) {
TwonlyScoreVisibility.nobody => context.lang.privacyVisibilityNobody,
TwonlyScoreVisibility.onlyContacts =>
context.lang.privacyVisibilityOnlyContacts,
TwonlyScoreVisibility.everyone => context.lang.privacyVisibilityEveryone,
};
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(context.lang.publicProfilePrivacyTitle)),
body: ListView(
padding: const EdgeInsets.only(top: 16),
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
context.lang.publicProfilePrivacyDescription,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: context.color.onSurfaceVariant,
),
),
),
const SizedBox(height: 12),
ListTile(
title: Text(context.lang.publicProfileTwonlyScore),
trailing: DropdownButtonHideUnderline(
child: DropdownButton<TwonlyScoreVisibility>(
value: userService.currentUser.twonlyScoreVisibility,
items: TwonlyScoreVisibility.values
.map(
(visibility) => DropdownMenuItem(
value: visibility,
child: Text(_visibilityLabel(visibility)),
),
)
.toList(),
onChanged: (visibility) {
if (visibility != null) {
_setTwonlyScoreVisibility(visibility);
}
},
),
),
),
],
),
);
}
}

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE contacts SET twonly_score = ? WHERE user_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "1b8401b1ec6cbb52dfc4ce32b2bf73900eec826b2ca2029a05a0125024ee071d"
}

View file

@ -321,6 +321,17 @@
"name": "widget_sharing_granted" "name": "widget_sharing_granted"
} }
} }
},
{
"name": "twonly_score",
"ordinal": 29,
"type_info": "Integer",
"origin": {
"Table": {
"table": "contacts",
"name": "twonly_score"
}
}
} }
], ],
"parameters": { "parameters": {
@ -355,7 +366,8 @@
false, false,
false, false,
false, false,
false false,
true
] ]
}, },
"hash": "c618a305e1355fcefa5c8420544779b5fe4f003a16ba01ee2834f028fabf213f" "hash": "c618a305e1355fcefa5c8420544779b5fe4f003a16ba01ee2834f028fabf213f"

View file

@ -794,6 +794,8 @@ async fn handle_encrypted_inner(
return contact::handle_contact_request(ctx, t, from_user_id, request).await; return contact::handle_contact_request(ctx, t, from_user_id, request).await;
} }
Contact::update_twonly_score(t, from_user_id, content.sender_twonly_score).await?;
if let Some(update) = content.contact_update { if let Some(update) = content.contact_update {
return contact::handle_contact_update( return contact::handle_contact_update(
ctx, ctx,
@ -970,6 +972,57 @@ async fn handle_encrypted_inner(
mod tests { mod tests {
use super::*; use super::*;
#[tokio::test]
async fn incoming_twonly_score_is_stored_and_missing_score_clears_it() -> Result<()> {
let directory = tempfile::tempdir()?;
let ctx = Context::init_for_testing(
directory.path().join("database"),
directory.path().join("data"),
)
.await?;
let database = ctx.app_db.read().await.clone();
sqlx::query("INSERT INTO contacts(user_id, username) VALUES (7, 'alice')")
.execute(&database.pool)
.await?;
let mut transaction = database.pool.begin().await?;
handle_encrypted_inner(
&ctx,
&mut transaction,
7,
"score-present",
proto::EncryptedContent {
sender_twonly_score: Some(23),
..Default::default()
},
)
.await?;
transaction.commit().await?;
let score: Option<i64> =
sqlx::query_scalar("SELECT twonly_score FROM contacts WHERE user_id = 7")
.fetch_one(&database.pool)
.await?;
assert_eq!(score, Some(23));
let mut transaction = database.pool.begin().await?;
handle_encrypted_inner(
&ctx,
&mut transaction,
7,
"score-missing",
proto::EncryptedContent::default(),
)
.await?;
transaction.commit().await?;
let score: Option<i64> =
sqlx::query_scalar("SELECT twonly_score FROM contacts WHERE user_id = 7")
.fetch_one(&database.pool)
.await?;
assert_eq!(score, None);
Ok(())
}
#[test] #[test]
fn copies_of_one_receipt_share_a_lock_until_the_last_one_goes() { fn copies_of_one_receipt_share_a_lock_until_the_last_one_goes() {
let receipt_id = "in-flight-registry-test"; let receipt_id = "in-flight-registry-test";

View file

@ -8,7 +8,7 @@ use crate::api::messages::incoming::messages;
use crate::api::proto::client as proto; use crate::api::proto::client as proto;
use crate::context::Context; use crate::context::Context;
use crate::error::Result; use crate::error::Result;
use crate::user_config::UserConfig; use crate::user_config::{TwonlyScoreVisibility, UserConfig};
use prost::Message as _; use prost::Message as _;
use sqlx::{Sqlite, Transaction}; use sqlx::{Sqlite, Transaction};
use std::sync::Arc; use std::sync::Arc;
@ -66,6 +66,31 @@ pub(crate) async fn decorate_content(
.unwrap_or(0) .unwrap_or(0)
!= 0, != 0,
); );
let share_twonly_score = match config.twonly_score_visibility {
TwonlyScoreVisibility::Nobody => false,
TwonlyScoreVisibility::OnlyContacts => {
sqlx::query_scalar!(
"SELECT accepted FROM contacts WHERE user_id = ?",
contact_id,
)
.fetch_optional(&mut **t)
.await?
.unwrap_or(0)
!= 0
}
TwonlyScoreVisibility::Everyone => true,
};
content.sender_twonly_score = if share_twonly_score {
Some(
sqlx::query_scalar::<_, i64>(
"SELECT COALESCE(SUM(total_media_counter), 0) FROM groups",
)
.fetch_one(&mut **t)
.await?,
)
} else {
None
};
if config.ask_for_friend_promotions { if config.ask_for_friend_promotions {
let accepted = sqlx::query_scalar!("SELECT COUNT(*) FROM contacts WHERE accepted = 1") let accepted = sqlx::query_scalar!("SELECT COUNT(*) FROM contacts WHERE accepted = 1")
.fetch_one(&mut **t) .fetch_one(&mut **t)
@ -203,3 +228,73 @@ pub async fn send_c2c_message_to_contact(
} }
Ok(None) Ok(None)
} }
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn twonly_score_respects_recipient_visibility() -> Result<()> {
let directory = tempfile::tempdir()?;
let ctx = Context::init_for_testing(
directory.path().join("database"),
directory.path().join("data"),
)
.await?;
UserConfig::save_json(
&ctx,
r#"{"userId":42,"username":"ben","displayName":"Ben"}"#,
)?;
let database = ctx.app_db.read().await.clone();
sqlx::query("INSERT INTO contacts(user_id, username) VALUES (7, 'anna')")
.execute(&database.pool)
.await?;
sqlx::query(
"INSERT INTO groups(group_id, group_name, total_media_counter) VALUES ('a', 'A', 4), ('b', 'B', 7)",
)
.execute(&database.pool)
.await?;
let mut content = proto::EncryptedContent::default();
let mut transaction = database.pool.begin().await?;
decorate_content(&ctx, &mut transaction, 7, &mut content, false).await?;
transaction.rollback().await?;
assert_eq!(content.sender_twonly_score, Some(11));
UserConfig::update(&ctx, |config| {
config.twonly_score_visibility = TwonlyScoreVisibility::OnlyContacts;
})?;
let mut hidden_content = proto::EncryptedContent {
sender_twonly_score: Some(999),
..Default::default()
};
let mut transaction = database.pool.begin().await?;
decorate_content(&ctx, &mut transaction, 7, &mut hidden_content, false).await?;
transaction.rollback().await?;
assert_eq!(hidden_content.sender_twonly_score, None);
sqlx::query("UPDATE contacts SET accepted = 1 WHERE user_id = 7")
.execute(&database.pool)
.await?;
let mut contact_content = proto::EncryptedContent::default();
let mut transaction = database.pool.begin().await?;
decorate_content(&ctx, &mut transaction, 7, &mut contact_content, false).await?;
transaction.rollback().await?;
assert_eq!(contact_content.sender_twonly_score, Some(11));
UserConfig::update(&ctx, |config| {
config.twonly_score_visibility = TwonlyScoreVisibility::Nobody;
})?;
let mut nobody_content = proto::EncryptedContent {
sender_twonly_score: Some(999),
..Default::default()
};
let mut transaction = database.pool.begin().await?;
decorate_content(&ctx, &mut transaction, 7, &mut nobody_content, false).await?;
transaction.rollback().await?;
assert_eq!(nobody_content.sender_twonly_score, None);
Ok(())
}
}

View file

@ -11,6 +11,8 @@ message EncryptedContent {
optional bool ask_for_friend_promotions = 25; optional bool ask_for_friend_promotions = 25;
reserved 28; reserved 28;
optional bool widget_sharing_allowed = 29; optional bool widget_sharing_allowed = 29;
// Sum of all exchanged media, omitted when profile sharing is disabled.
optional int64 sender_twonly_score = 31;
optional MessageUpdate message_update = 5; optional MessageUpdate message_update = 5;
optional Media media = 6; optional Media media = 6;

View file

@ -0,0 +1,3 @@
-- A contact's latest publicly shared twonly Score. NULL means that the
-- contact does not currently share it.
ALTER TABLE contacts ADD COLUMN twonly_score INTEGER;

View file

@ -16,7 +16,7 @@ mod legacy_import;
pub mod tables; pub mod tables;
pub const APP_DATABASE_FILE: &str = "app_db.sqlite"; pub const APP_DATABASE_FILE: &str = "app_db.sqlite";
pub const APP_SCHEMA_VERSION: i64 = 13; pub const APP_SCHEMA_VERSION: i64 = 14;
/// User-owned application tables in the current Rust schema. Rust-only outbox /// User-owned application tables in the current Rust schema. Rust-only outbox
/// tables are deliberately absent because they are reconstructed locally. /// tables are deliberately absent because they are reconstructed locally.

View file

@ -41,6 +41,7 @@ pub struct Contact {
pub media_received_counter: i64, pub media_received_counter: i64,
pub widget_sharing_allowed: i64, pub widget_sharing_allowed: i64,
pub widget_sharing_granted: i64, pub widget_sharing_granted: i64,
pub twonly_score: Option<i64>,
} }
#[derive(bon::Builder)] #[derive(bon::Builder)]
@ -206,6 +207,21 @@ impl Contact {
Ok(()) Ok(())
} }
pub async fn update_twonly_score(
t: &mut Transaction<'_, Sqlite>,
user_id: i64,
score: Option<i64>,
) -> Result<()> {
sqlx::query!(
"UPDATE contacts SET twonly_score = ? WHERE user_id = ?",
score,
user_id,
)
.execute(&mut **t)
.await?;
Ok(())
}
pub async fn is_user_discovery_allowed( pub async fn is_user_discovery_allowed(
context: &Context, context: &Context,
t: &mut Transaction<'_, Sqlite>, t: &mut Transaction<'_, Sqlite>,

View file

@ -7830,6 +7830,19 @@ impl SseDecode for crate::user_config::TwonlySafeBackup {
} }
} }
impl SseDecode for crate::user_config::TwonlyScoreVisibility {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <i32>::sse_decode(deserializer);
return match inner {
0 => crate::user_config::TwonlyScoreVisibility::Nobody,
1 => crate::user_config::TwonlyScoreVisibility::OnlyContacts,
2 => crate::user_config::TwonlyScoreVisibility::Everyone,
_ => unreachable!("Invalid variant for TwonlyScoreVisibility: {}", inner),
};
}
}
impl SseDecode for u16 { impl SseDecode for u16 {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -7918,6 +7931,8 @@ impl SseDecode for crate::user_config::UserConfig {
let mut var_allowErrorTrackingViaSentry = <bool>::sse_decode(deserializer); let mut var_allowErrorTrackingViaSentry = <bool>::sse_decode(deserializer);
let mut var_screenLockEnabled = <bool>::sse_decode(deserializer); let mut var_screenLockEnabled = <bool>::sse_decode(deserializer);
let mut var_isCloudBackupEnabled = <bool>::sse_decode(deserializer); let mut var_isCloudBackupEnabled = <bool>::sse_decode(deserializer);
let mut var_twonlyScoreVisibility =
<crate::user_config::TwonlyScoreVisibility>::sse_decode(deserializer);
let mut var_isUserDiscoveryEnabled = <bool>::sse_decode(deserializer); let mut var_isUserDiscoveryEnabled = <bool>::sse_decode(deserializer);
let mut var_requiredSendImages = <i64>::sse_decode(deserializer); let mut var_requiredSendImages = <i64>::sse_decode(deserializer);
let mut var_userDiscoveryThreshold = <u8>::sse_decode(deserializer); let mut var_userDiscoveryThreshold = <u8>::sse_decode(deserializer);
@ -7986,6 +8001,7 @@ impl SseDecode for crate::user_config::UserConfig {
allow_error_tracking_via_sentry: var_allowErrorTrackingViaSentry, allow_error_tracking_via_sentry: var_allowErrorTrackingViaSentry,
screen_lock_enabled: var_screenLockEnabled, screen_lock_enabled: var_screenLockEnabled,
is_cloud_backup_enabled: var_isCloudBackupEnabled, is_cloud_backup_enabled: var_isCloudBackupEnabled,
twonly_score_visibility: var_twonlyScoreVisibility,
is_user_discovery_enabled: var_isUserDiscoveryEnabled, is_user_discovery_enabled: var_isUserDiscoveryEnabled,
required_send_images: var_requiredSendImages, required_send_images: var_requiredSendImages,
user_discovery_threshold: var_userDiscoveryThreshold, user_discovery_threshold: var_userDiscoveryThreshold,
@ -9202,6 +9218,28 @@ impl flutter_rust_bridge::IntoIntoDart<crate::user_config::TwonlySafeBackup>
} }
} }
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::user_config::TwonlyScoreVisibility {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
match self {
Self::Nobody => 0.into_dart(),
Self::OnlyContacts => 1.into_dart(),
Self::Everyone => 2.into_dart(),
_ => unreachable!(),
}
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::user_config::TwonlyScoreVisibility
{
}
impl flutter_rust_bridge::IntoIntoDart<crate::user_config::TwonlyScoreVisibility>
for crate::user_config::TwonlyScoreVisibility
{
fn into_into_dart(self) -> crate::user_config::TwonlyScoreVisibility {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::user_config::UserConfig { impl flutter_rust_bridge::IntoDart for crate::user_config::UserConfig {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[ [
@ -9261,6 +9299,7 @@ impl flutter_rust_bridge::IntoDart for crate::user_config::UserConfig {
.into_dart(), .into_dart(),
self.screen_lock_enabled.into_into_dart().into_dart(), self.screen_lock_enabled.into_into_dart().into_dart(),
self.is_cloud_backup_enabled.into_into_dart().into_dart(), self.is_cloud_backup_enabled.into_into_dart().into_dart(),
self.twonly_score_visibility.into_into_dart().into_dart(),
self.is_user_discovery_enabled.into_into_dart().into_dart(), self.is_user_discovery_enabled.into_into_dart().into_dart(),
self.required_send_images.into_into_dart().into_dart(), self.required_send_images.into_into_dart().into_dart(),
self.user_discovery_threshold.into_into_dart().into_dart(), self.user_discovery_threshold.into_into_dart().into_dart(),
@ -10344,6 +10383,23 @@ impl SseEncode for crate::user_config::TwonlySafeBackup {
} }
} }
impl SseEncode for crate::user_config::TwonlyScoreVisibility {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<i32>::sse_encode(
match self {
crate::user_config::TwonlyScoreVisibility::Nobody => 0,
crate::user_config::TwonlyScoreVisibility::OnlyContacts => 1,
crate::user_config::TwonlyScoreVisibility::Everyone => 2,
_ => {
unimplemented!("");
}
},
serializer,
);
}
}
impl SseEncode for u16 { impl SseEncode for u16 {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -10445,6 +10501,10 @@ impl SseEncode for crate::user_config::UserConfig {
<bool>::sse_encode(self.allow_error_tracking_via_sentry, serializer); <bool>::sse_encode(self.allow_error_tracking_via_sentry, serializer);
<bool>::sse_encode(self.screen_lock_enabled, serializer); <bool>::sse_encode(self.screen_lock_enabled, serializer);
<bool>::sse_encode(self.is_cloud_backup_enabled, serializer); <bool>::sse_encode(self.is_cloud_backup_enabled, serializer);
<crate::user_config::TwonlyScoreVisibility>::sse_encode(
self.twonly_score_visibility,
serializer,
);
<bool>::sse_encode(self.is_user_discovery_enabled, serializer); <bool>::sse_encode(self.is_user_discovery_enabled, serializer);
<i64>::sse_encode(self.required_send_images, serializer); <i64>::sse_encode(self.required_send_images, serializer);
<u8>::sse_encode(self.user_discovery_threshold, serializer); <u8>::sse_encode(self.user_discovery_threshold, serializer);

View file

@ -166,6 +166,41 @@ pub enum ThemeMode {
Dark, Dark,
} }
#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum TwonlyScoreVisibility {
Nobody,
OnlyContacts,
#[default]
Everyone,
}
impl<'de> Deserialize<'de> for TwonlyScoreVisibility {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::String(value) => match value.as_str() {
"nobody" => Ok(Self::Nobody),
"onlyContacts" => Ok(Self::OnlyContacts),
"everyone" => Ok(Self::Everyone),
_ => Err(serde::de::Error::custom(format!(
"unknown twonly Score visibility: {value}"
))),
},
// Migrate the boolean setting used by the first version of this
// feature without changing an existing privacy choice.
serde_json::Value::Bool(true) => Ok(Self::Everyone),
serde_json::Value::Bool(false) => Ok(Self::Nobody),
_ => Err(serde::de::Error::custom(
"twonly Score visibility must be a visibility name",
)),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[flutter_rust_bridge::frb(non_final)] #[flutter_rust_bridge::frb(non_final)]
@ -288,6 +323,11 @@ pub struct UserConfig {
#[frb(non_final)] #[frb(non_final)]
pub is_cloud_backup_enabled: bool, pub is_cloud_backup_enabled: bool,
/// Controls which recipients receive the current twonly Score in sender metadata.
#[serde(default, alias = "shareTwonlyScore")]
#[frb(non_final)]
pub twonly_score_visibility: TwonlyScoreVisibility,
#[serde(default)] #[serde(default)]
#[frb(non_final)] #[frb(non_final)]
pub is_user_discovery_enabled: bool, pub is_user_discovery_enabled: bool,
@ -480,7 +520,9 @@ impl UserConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{merge_changed_fields, UserConfig, MIN_USER_DISCOVERY_THRESHOLD}; use super::{
merge_changed_fields, TwonlyScoreVisibility, UserConfig, MIN_USER_DISCOVERY_THRESHOLD,
};
#[test] #[test]
fn parses_only_rust_fields_and_applies_defaults() { fn parses_only_rust_fields_and_applies_defaults() {
@ -503,10 +545,45 @@ mod tests {
); );
assert!(config.can_use_login_token_for_auth); assert!(config.can_use_login_token_for_auth);
assert_eq!(
config.twonly_score_visibility,
TwonlyScoreVisibility::Everyone
);
assert!(!config.is_user_discovery_enabled); assert!(!config.is_user_discovery_enabled);
assert_eq!(config.last_server_message_at, None); assert_eq!(config.last_server_message_at, None);
} }
#[test]
fn migrates_legacy_twonly_score_boolean() {
let hidden: UserConfig = serde_json::from_str(
r#"{
"userId": 42,
"username": "alice",
"displayName": "Alice",
"shareTwonlyScore": false
}"#,
)
.unwrap();
assert_eq!(
hidden.twonly_score_visibility,
TwonlyScoreVisibility::Nobody
);
let shared: UserConfig = serde_json::from_str(
r#"{
"userId": 42,
"username": "alice",
"displayName": "Alice",
"shareTwonlyScore": true
}"#,
)
.unwrap();
assert_eq!(
shared.twonly_score_visibility,
TwonlyScoreVisibility::Everyone
);
}
#[test] #[test]
fn normalizes_legacy_user_discovery_threshold() { fn normalizes_legacy_user_discovery_threshold() {
let mut config: UserConfig = serde_json::from_str( let mut config: UserConfig = serde_json::from_str(

View file

@ -35,6 +35,7 @@ UserConfig testUserConfig({
allowErrorTrackingViaSentry: false, allowErrorTrackingViaSentry: false,
screenLockEnabled: false, screenLockEnabled: false,
isCloudBackupEnabled: false, isCloudBackupEnabled: false,
twonlyScoreVisibility: TwonlyScoreVisibility.everyone,
isUserDiscoveryEnabled: false, isUserDiscoveryEnabled: false,
requiredSendImages: 4, requiredSendImages: 4,
userDiscoveryThreshold: 3, userDiscoveryThreshold: 3,