From 854121c8c4fa141197f2983623c20d2d13769701 Mon Sep 17 00:00:00 2001 From: otsmr Date: Tue, 6 Oct 2026 22:14:27 +0200 Subject: [PATCH] person avatar --- dependencies | 2 +- lib/core/bridge/api.dart | 21 + lib/core/frb_generated.dart | 455 ++++++++++----- lib/core/frb_generated.io.dart | 13 + lib/core/frb_generated.web.dart | 13 + lib/core/services/avatars.dart | 46 ++ lib/src/constants/keyvalue.keys.dart | 2 + .../generated/app_localizations.dart | 72 +++ .../generated/app_localizations_ar.dart | 37 ++ .../generated/app_localizations_de.dart | 38 ++ .../generated/app_localizations_en.dart | 38 ++ lib/src/localization/translations/ar.arb | 12 + lib/src/localization/translations/de.arb | 12 + lib/src/localization/translations/en.arb | 12 + .../client/generated/messages.pb.dart | 194 +++++++ .../client/generated/messages.pbenum.dart | 21 + .../client/generated/messages.pbjson.dart | 381 ++++++++----- .../visual/components/avatar_icon.comp.dart | 49 +- .../story_target_selector.comp.dart | 5 +- .../views/camera/share_image_editor.view.dart | 28 +- .../memories/sticker_source_picker.view.dart | 10 +- .../settings/privacy/public_profile.view.dart | 63 +++ .../settings/profile/modify_avatar.view.dart | 414 +++++++++++--- rust/src/api/messages/incoming.rs | 1 + rust/src/api/messages/incoming/contact.rs | 96 ++++ rust/src/api/messages/incoming/messages.rs | 3 + rust/src/api/messages/outgoing.rs | 4 + rust/src/api/proto/client/messages.proto | 23 + rust/src/bridge/api.rs | 30 + .../app/migrations/0031_custom_avatars.sql | 31 ++ rust/src/database/app/mod.rs | 2 +- rust/src/frb_generated.rs | 475 +++++++++++----- rust/src/services/avatars.rs | 525 +++++++++++++++++- rust/src/services/contacts.rs | 2 + rust/src/services/notifications.rs | 31 +- ...vatar_maker_customizer_lifecycle_test.dart | 29 + 36 files changed, 2663 insertions(+), 527 deletions(-) create mode 100644 lib/core/services/avatars.dart create mode 100644 rust/src/database/app/migrations/0031_custom_avatars.sql create mode 100644 test/visual/components/avatar_maker_customizer_lifecycle_test.dart diff --git a/dependencies b/dependencies index 8cf0eb53..c3d08588 160000 --- a/dependencies +++ b/dependencies @@ -1 +1 @@ -Subproject commit 8cf0eb53c6955201bea92d4515e0978e383640b3 +Subproject commit c3d085882368d7269956048bbb69fbd8a69a711c diff --git a/lib/core/bridge/api.dart b/lib/core/bridge/api.dart index 17f15ad1..d8683038 100644 --- a/lib/core/bridge/api.dart +++ b/lib/core/bridge/api.dart @@ -5,6 +5,7 @@ import '../api/server/prekeys.dart'; import '../frb_generated.dart'; +import '../services/avatars.dart'; import '../services/media_upload.dart'; import '../services/stickers.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; @@ -353,6 +354,14 @@ class RustApi { static Future connectionState() => RustLib.instance.api.crateBridgeApiRustApiConnectionState(); + static Future createCustomAvatar({ + required String imagePath, + required bool acceptedContactsOnly, + }) => RustLib.instance.api.crateBridgeApiRustApiCreateCustomAvatar( + imagePath: imagePath, + acceptedContactsOnly: acceptedContactsOnly, + ); + /// Creates a transparent, message-sized sticker from a local image. Model /// inference and image encoding are CPU-heavy, so neither runs on Tokio's /// async worker threads. @@ -426,6 +435,9 @@ class RustApi { static Future forceIpaCheck() => RustLib.instance.api.crateBridgeApiRustApiForceIpaCheck(); + static Future getCustomAvatar() => + RustLib.instance.api.crateBridgeApiRustApiGetCustomAvatar(); + static Future getMemoriesUrl({ required String mediaId, required bool thumbnail, @@ -667,6 +679,9 @@ class RustApi { userId: userId, ); + static Future removeCustomAvatar() => + RustLib.instance.api.crateBridgeApiRustApiRemoveCustomAvatar(); + /// Deletes every file of a media item while keeping its row. static Future removeMediaFiles({required String mediaId}) => RustLib .instance @@ -800,6 +815,12 @@ class RustApi { .api .crateBridgeApiRustApiSetBackground(inBackground: inBackground); + static Future setCustomAvatarAudience({ + required bool acceptedContactsOnly, + }) => RustLib.instance.api.crateBridgeApiRustApiSetCustomAvatarAudience( + acceptedContactsOnly: acceptedContactsOnly, + ); + static Future setHomeWidgetGroups({ required String widgetId, required String platform, diff --git a/lib/core/frb_generated.dart b/lib/core/frb_generated.dart index 0f048d8a..f4b719e5 100644 --- a/lib/core/frb_generated.dart +++ b/lib/core/frb_generated.dart @@ -27,6 +27,7 @@ import 'frb_generated.io.dart' import 'keys/backup_password_keys.dart'; import 'lib.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'services/avatars.dart'; import 'services/media_upload.dart'; import 'services/stickers.dart'; import 'signal/engine.dart'; @@ -89,7 +90,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.12.0'; @override - int get rustContentHash => -1901082359; + int get rustContentHash => 926647182; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -271,6 +272,11 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeApiRustApiConnectionState(); + Future crateBridgeApiRustApiCreateCustomAvatar({ + required String imagePath, + required bool acceptedContactsOnly, + }); + Future crateBridgeApiRustApiCreateSticker({ required String imagePath, }); @@ -316,6 +322,8 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeApiRustApiForceIpaCheck(); + Future crateBridgeApiRustApiGetCustomAvatar(); + Future crateBridgeApiRustApiGetMemoriesUrl({ required String mediaId, required bool thumbnail, @@ -451,6 +459,8 @@ abstract class RustLibApi extends BaseApi { required PlatformInt64 userId, }); + Future crateBridgeApiRustApiRemoveCustomAvatar(); + Future crateBridgeApiRustApiRemoveMediaFiles({required String mediaId}); Future crateBridgeApiRustApiReportUser({ @@ -532,6 +542,10 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeApiRustApiSetBackground({required bool inBackground}); + Future crateBridgeApiRustApiSetCustomAvatarAudience({ + required bool acceptedContactsOnly, + }); + Future crateBridgeApiRustApiSetHomeWidgetGroups({ required String widgetId, required String platform, @@ -2496,6 +2510,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: [], ); + @override + Future crateBridgeApiRustApiCreateCustomAvatar({ + required String imagePath, + required bool acceptedContactsOnly, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(imagePath, serializer); + sse_encode_bool(acceptedContactsOnly, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 52, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_custom_avatar_info, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiCreateCustomAvatarConstMeta, + argValues: [imagePath, acceptedContactsOnly], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiCreateCustomAvatarConstMeta => + const TaskConstMeta( + debugName: "rust_api_create_custom_avatar", + argNames: ["imagePath", "acceptedContactsOnly"], + ); + @override Future crateBridgeApiRustApiCreateSticker({ required String imagePath, @@ -2508,7 +2557,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 52, + funcId: 53, port: port_, ); }, @@ -2541,7 +2590,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 53, + funcId: 54, port: port_, ); }, @@ -2572,7 +2621,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 54, + funcId: 55, port: port_, ); }, @@ -2602,7 +2651,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(avatarSvgCompressed, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 55)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 56)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -2630,7 +2679,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 56, + funcId: 57, port: port_, ); }, @@ -2661,7 +2710,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 57, + funcId: 58, port: port_, ); }, @@ -2694,7 +2743,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 58, + funcId: 59, port: port_, ); }, @@ -2724,7 +2773,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 59, + funcId: 60, port: port_, ); }, @@ -2755,7 +2804,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 60, + funcId: 61, port: port_, ); }, @@ -2786,7 +2835,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 61, + funcId: 62, port: port_, ); }, @@ -2816,7 +2865,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 62, + funcId: 63, port: port_, ); }, @@ -2849,7 +2898,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 63, + funcId: 64, port: port_, ); }, @@ -2884,7 +2933,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 64, + funcId: 65, port: port_, ); }, @@ -2917,7 +2966,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 65, + funcId: 66, port: port_, ); }, @@ -2949,7 +2998,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 66, + funcId: 67, port: port_, ); }, @@ -2979,7 +3028,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 67, + funcId: 68, port: port_, ); }, @@ -3000,6 +3049,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: [], ); + @override + Future crateBridgeApiRustApiGetCustomAvatar() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 69, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_custom_avatar_info, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiGetCustomAvatarConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiGetCustomAvatarConstMeta => + const TaskConstMeta( + debugName: "rust_api_get_custom_avatar", + argNames: [], + ); + @override Future crateBridgeApiRustApiGetMemoriesUrl({ required String mediaId, @@ -3014,7 +3093,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 68, + funcId: 70, port: port_, ); }, @@ -3044,7 +3123,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 69, + funcId: 71, port: port_, ); }, @@ -3074,7 +3153,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 70, + funcId: 72, port: port_, ); }, @@ -3104,7 +3183,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 71, + funcId: 73, port: port_, ); }, @@ -3145,7 +3224,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 72, + funcId: 74, port: port_, ); }, @@ -3192,7 +3271,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 73, + funcId: 75, port: port_, ); }, @@ -3225,7 +3304,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 74, + funcId: 76, port: port_, ); }, @@ -3258,7 +3337,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 75, + funcId: 77, port: port_, ); }, @@ -3288,7 +3367,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 76, + funcId: 78, port: port_, ); }, @@ -3328,7 +3407,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 77, + funcId: 79, port: port_, ); }, @@ -3361,7 +3440,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 78, + funcId: 80, port: port_, ); }, @@ -3400,7 +3479,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 79, + funcId: 81, port: port_, ); }, @@ -3436,7 +3515,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 80, + funcId: 82, port: port_, ); }, @@ -3471,7 +3550,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 81, + funcId: 83, port: port_, ); }, @@ -3513,7 +3592,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 82, + funcId: 84, port: port_, ); }, @@ -3555,7 +3634,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 83, + funcId: 85, port: port_, ); }, @@ -3585,7 +3664,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 84, + funcId: 86, port: port_, ); }, @@ -3618,7 +3697,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 85, + funcId: 87, port: port_, ); }, @@ -3653,7 +3732,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 86, + funcId: 88, port: port_, ); }, @@ -3683,7 +3762,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 87, + funcId: 89, port: port_, ); }, @@ -3718,7 +3797,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 88, + funcId: 90, port: port_, ); }, @@ -3748,7 +3827,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 89, + funcId: 91, port: port_, ); }, @@ -3781,7 +3860,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 90, + funcId: 92, port: port_, ); }, @@ -3811,7 +3890,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 91, + funcId: 93, port: port_, ); }, @@ -3841,7 +3920,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 92, + funcId: 94, port: port_, ); }, @@ -3871,7 +3950,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 93, + funcId: 95, port: port_, ); }, @@ -3901,7 +3980,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 94, + funcId: 96, port: port_, ); }, @@ -3931,7 +4010,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 95, + funcId: 97, port: port_, ); }, @@ -3970,7 +4049,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 96, + funcId: 98, port: port_, ); }, @@ -4005,7 +4084,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 97, + funcId: 99, port: port_, ); }, @@ -4044,7 +4123,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 98, + funcId: 100, port: port_, ); }, @@ -4086,7 +4165,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 99, + funcId: 101, port: port_, ); }, @@ -4117,7 +4196,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 100, + funcId: 102, port: port_, ); }, @@ -4150,7 +4229,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 101, + funcId: 103, port: port_, ); }, @@ -4171,6 +4250,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["userId"], ); + @override + Future crateBridgeApiRustApiRemoveCustomAvatar() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 104, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_custom_avatar_info, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiRemoveCustomAvatarConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiRemoveCustomAvatarConstMeta => + const TaskConstMeta( + debugName: "rust_api_remove_custom_avatar", + argNames: [], + ); + @override Future crateBridgeApiRustApiRemoveMediaFiles({ required String mediaId, @@ -4183,7 +4292,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 102, + funcId: 105, port: port_, ); }, @@ -4218,7 +4327,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 103, + funcId: 106, port: port_, ); }, @@ -4251,7 +4360,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 104, + funcId: 107, port: port_, ); }, @@ -4284,7 +4393,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 105, + funcId: 108, port: port_, ); }, @@ -4317,7 +4426,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 106, + funcId: 109, port: port_, ); }, @@ -4354,7 +4463,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 107, + funcId: 110, port: port_, ); }, @@ -4384,7 +4493,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 108, + funcId: 111, port: port_, ); }, @@ -4414,7 +4523,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 109, + funcId: 112, port: port_, ); }, @@ -4444,7 +4553,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 110, + funcId: 113, port: port_, ); }, @@ -4477,7 +4586,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 111, + funcId: 114, port: port_, ); }, @@ -4508,7 +4617,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 112, + funcId: 115, port: port_, ); }, @@ -4541,7 +4650,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 113, + funcId: 116, port: port_, ); }, @@ -4587,7 +4696,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 114, + funcId: 117, port: port_, ); }, @@ -4643,7 +4752,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 115, + funcId: 118, port: port_, ); }, @@ -4693,7 +4802,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 116, + funcId: 119, port: port_, ); }, @@ -4738,7 +4847,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 117, + funcId: 120, port: port_, ); }, @@ -4773,7 +4882,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 118, + funcId: 121, port: port_, ); }, @@ -4808,7 +4917,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 119, + funcId: 122, port: port_, ); }, @@ -4841,7 +4950,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 120, + funcId: 123, port: port_, ); }, @@ -4862,6 +4971,39 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["inBackground"], ); + @override + Future crateBridgeApiRustApiSetCustomAvatarAudience({ + required bool acceptedContactsOnly, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_bool(acceptedContactsOnly, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 124, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_custom_avatar_info, + decodeErrorData: sse_decode_AnyhowException, + ), + constMeta: kCrateBridgeApiRustApiSetCustomAvatarAudienceConstMeta, + argValues: [acceptedContactsOnly], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateBridgeApiRustApiSetCustomAvatarAudienceConstMeta => + const TaskConstMeta( + debugName: "rust_api_set_custom_avatar_audience", + argNames: ["acceptedContactsOnly"], + ); + @override Future crateBridgeApiRustApiSetHomeWidgetGroups({ required String widgetId, @@ -4878,7 +5020,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 121, + funcId: 125, port: port_, ); }, @@ -4909,7 +5051,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 122, + funcId: 126, port: port_, ); }, @@ -4947,7 +5089,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 123, + funcId: 127, port: port_, ); }, @@ -4982,7 +5124,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 124, + funcId: 128, port: port_, ); }, @@ -5021,7 +5163,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 125, + funcId: 129, port: port_, ); }, @@ -5054,7 +5196,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 126, + funcId: 130, port: port_, ); }, @@ -5085,7 +5227,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 127, + funcId: 131, port: port_, ); }, @@ -5120,7 +5262,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 128, + funcId: 132, port: port_, ); }, @@ -5150,7 +5292,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 129, + funcId: 133, port: port_, ); }, @@ -5183,7 +5325,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 130, + funcId: 134, port: port_, ); }, @@ -5218,7 +5360,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 131, + funcId: 135, port: port_, ); }, @@ -5251,7 +5393,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 132, + funcId: 136, port: port_, ); }, @@ -5282,7 +5424,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 133, + funcId: 137, port: port_, ); }, @@ -5319,7 +5461,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 134, + funcId: 138, port: port_, ); }, @@ -5371,7 +5513,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 135, + funcId: 139, port: port_, ); }, @@ -5424,7 +5566,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 136, + funcId: 140, port: port_, ); }, @@ -5464,7 +5606,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 137, + funcId: 141, port: port_, ); }, @@ -5497,7 +5639,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 138, + funcId: 142, port: port_, ); }, @@ -5530,7 +5672,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 139, + funcId: 143, port: port_, ); }, @@ -5567,7 +5709,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 140, + funcId: 144, port: port_, ); }, @@ -5599,7 +5741,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 141, + funcId: 145, port: port_, ); }, @@ -5632,7 +5774,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 142, + funcId: 146, port: port_, ); }, @@ -5667,7 +5809,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 143, + funcId: 147, port: port_, ); }, @@ -5699,7 +5841,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 144, + funcId: 148, port: port_, ); }, @@ -5737,7 +5879,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 145, + funcId: 149, port: port_, ); }, @@ -5770,7 +5912,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 146, + funcId: 150, port: port_, ); }, @@ -5808,7 +5950,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 147, + funcId: 151, port: port_, ); }, @@ -5845,7 +5987,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 148, + funcId: 152, port: port_, ); }, @@ -5882,7 +6024,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 149, + funcId: 153, port: port_, ); }, @@ -5920,7 +6062,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 150, + funcId: 154, port: port_, ); }, @@ -5958,7 +6100,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 151, + funcId: 155, port: port_, ); }, @@ -5991,7 +6133,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 152, + funcId: 156, port: port_, ); }, @@ -6023,7 +6165,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 153, + funcId: 157, port: port_, ); }, @@ -6058,7 +6200,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 154, + funcId: 158, port: port_, ); }, @@ -6095,7 +6237,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 155, + funcId: 159, port: port_, ); }, @@ -6128,7 +6270,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 156, + funcId: 160, port: port_, ); }, @@ -6160,7 +6302,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 157, + funcId: 161, port: port_, ); }, @@ -6195,7 +6337,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 158, + funcId: 162, port: port_, ); }, @@ -6234,7 +6376,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 159, + funcId: 163, port: port_, ); }, @@ -6271,7 +6413,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 160, + funcId: 164, port: port_, ); }, @@ -6301,7 +6443,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 161, + funcId: 165, port: port_, ); }, @@ -6333,7 +6475,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 162, + funcId: 166, port: port_, ); }, @@ -6368,7 +6510,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 163, + funcId: 167, port: port_, ); }, @@ -6403,7 +6545,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 164, + funcId: 168, port: port_, ); }, @@ -6434,7 +6576,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 165, + funcId: 169, port: port_, ); }, @@ -6472,7 +6614,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 166, + funcId: 170, port: port_, ); }, @@ -6507,7 +6649,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 167, + funcId: 171, port: port_, ); }, @@ -6546,7 +6688,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 168, + funcId: 172, port: port_, ); }, @@ -6581,7 +6723,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 169, + funcId: 173, port: port_, ); }, @@ -6626,7 +6768,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 170, + funcId: 174, port: port_, ); }, @@ -6669,7 +6811,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 171, + funcId: 175, port: port_, ); }, @@ -6701,7 +6843,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 172, + funcId: 176, port: port_, ); }, @@ -6736,7 +6878,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 173, + funcId: 177, port: port_, ); }, @@ -6771,7 +6913,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 174, + funcId: 178, port: port_, ); }, @@ -6806,7 +6948,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 175, + funcId: 179, port: port_, ); }, @@ -6839,7 +6981,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 176, + funcId: 180, )!; }, codec: SseCodec( @@ -6879,7 +7021,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 177, + funcId: 181, port: port_, ); }, @@ -6924,7 +7066,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 178, + funcId: 182, port: port_, ); }, @@ -6954,7 +7096,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 179, + funcId: 183, port: port_, ); }, @@ -6987,7 +7129,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 180, + funcId: 184, port: port_, ); }, @@ -7022,7 +7164,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 181, + funcId: 185, port: port_, ); }, @@ -7061,7 +7203,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 182, + funcId: 186, )!; }, codec: SseCodec( @@ -7370,6 +7512,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_webxdc_instance_info(raw); } + @protected + CustomAvatarInfo dco_decode_custom_avatar_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 6) + throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + return CustomAvatarInfo( + webp: dco_decode_opt_list_prim_u_8_strict(arr[0]), + sha256: dco_decode_opt_list_prim_u_8_strict(arr[1]), + width: dco_decode_opt_box_autoadd_u_32(arr[2]), + height: dco_decode_opt_box_autoadd_u_32(arr[3]), + acceptedContactsOnly: dco_decode_bool(arr[4]), + sourceRevision: dco_decode_i_64(arr[5]), + ); + } + @protected double dco_decode_f_64(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -8559,6 +8717,25 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_webxdc_instance_info(deserializer)); } + @protected + CustomAvatarInfo sse_decode_custom_avatar_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_webp = sse_decode_opt_list_prim_u_8_strict(deserializer); + var var_sha256 = sse_decode_opt_list_prim_u_8_strict(deserializer); + var var_width = sse_decode_opt_box_autoadd_u_32(deserializer); + var var_height = sse_decode_opt_box_autoadd_u_32(deserializer); + var var_acceptedContactsOnly = sse_decode_bool(deserializer); + var var_sourceRevision = sse_decode_i_64(deserializer); + return CustomAvatarInfo( + webp: var_webp, + sha256: var_sha256, + width: var_width, + height: var_height, + acceptedContactsOnly: var_acceptedContactsOnly, + sourceRevision: var_sourceRevision, + ); + } + @protected double sse_decode_f_64(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -10107,6 +10284,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_webxdc_instance_info(self, serializer); } + @protected + void sse_encode_custom_avatar_info( + CustomAvatarInfo self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_list_prim_u_8_strict(self.webp, serializer); + sse_encode_opt_list_prim_u_8_strict(self.sha256, serializer); + sse_encode_opt_box_autoadd_u_32(self.width, serializer); + sse_encode_opt_box_autoadd_u_32(self.height, serializer); + sse_encode_bool(self.acceptedContactsOnly, serializer); + sse_encode_i_64(self.sourceRevision, serializer); + } + @protected void sse_encode_f_64(double self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/core/frb_generated.io.dart b/lib/core/frb_generated.io.dart index da5089ec..0bcabdef 100644 --- a/lib/core/frb_generated.io.dart +++ b/lib/core/frb_generated.io.dart @@ -26,6 +26,7 @@ import 'frb_generated.dart'; import 'keys/backup_password_keys.dart'; import 'lib.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; +import 'services/avatars.dart'; import 'services/media_upload.dart'; import 'services/stickers.dart'; import 'signal/engine.dart'; @@ -137,6 +138,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected WebxdcInstanceInfo dco_decode_box_autoadd_webxdc_instance_info(dynamic raw); + @protected + CustomAvatarInfo dco_decode_custom_avatar_info(dynamic raw); + @protected double dco_decode_f_64(dynamic raw); @@ -559,6 +563,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + CustomAvatarInfo sse_decode_custom_avatar_info(SseDeserializer deserializer); + @protected double sse_decode_f_64(SseDeserializer deserializer); @@ -1078,6 +1085,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_custom_avatar_info( + CustomAvatarInfo self, + SseSerializer serializer, + ); + @protected void sse_encode_f_64(double self, SseSerializer serializer); diff --git a/lib/core/frb_generated.web.dart b/lib/core/frb_generated.web.dart index ef90dbf4..7b20d01e 100644 --- a/lib/core/frb_generated.web.dart +++ b/lib/core/frb_generated.web.dart @@ -28,6 +28,7 @@ import 'frb_generated.dart'; import 'keys/backup_password_keys.dart'; import 'lib.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; +import 'services/avatars.dart'; import 'services/media_upload.dart'; import 'services/stickers.dart'; import 'signal/engine.dart'; @@ -139,6 +140,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected WebxdcInstanceInfo dco_decode_box_autoadd_webxdc_instance_info(dynamic raw); + @protected + CustomAvatarInfo dco_decode_custom_avatar_info(dynamic raw); + @protected double dco_decode_f_64(dynamic raw); @@ -561,6 +565,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + CustomAvatarInfo sse_decode_custom_avatar_info(SseDeserializer deserializer); + @protected double sse_decode_f_64(SseDeserializer deserializer); @@ -1080,6 +1087,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_custom_avatar_info( + CustomAvatarInfo self, + SseSerializer serializer, + ); + @protected void sse_encode_f_64(double self, SseSerializer serializer); diff --git a/lib/core/services/avatars.dart b/lib/core/services/avatars.dart new file mode 100644 index 00000000..bd03a616 --- /dev/null +++ b/lib/core/services/avatars.dart @@ -0,0 +1,46 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +class CustomAvatarInfo { + final Uint8List? webp; + final Uint8List? sha256; + final int? width; + final int? height; + final bool acceptedContactsOnly; + final PlatformInt64 sourceRevision; + + const CustomAvatarInfo({ + this.webp, + this.sha256, + this.width, + this.height, + required this.acceptedContactsOnly, + required this.sourceRevision, + }); + + @override + int get hashCode => + webp.hashCode ^ + sha256.hashCode ^ + width.hashCode ^ + height.hashCode ^ + acceptedContactsOnly.hashCode ^ + sourceRevision.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CustomAvatarInfo && + runtimeType == other.runtimeType && + webp == other.webp && + sha256 == other.sha256 && + width == other.width && + height == other.height && + acceptedContactsOnly == other.acceptedContactsOnly && + sourceRevision == other.sourceRevision; +} diff --git a/lib/src/constants/keyvalue.keys.dart b/lib/src/constants/keyvalue.keys.dart index 9f3bee56..ab4f16e9 100644 --- a/lib/src/constants/keyvalue.keys.dart +++ b/lib/src/constants/keyvalue.keys.dart @@ -2,4 +2,6 @@ class KeyValueKeys { static const String currentBackupState = 'current_backup_state'; static const String backupRecoveryState = 'backup_recovery_state'; static const String onboardingState = 'onboarding_state'; + static const String shareImageWidgetExplainer = + 'share_image_widget_explainer'; } diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 88f5ec40..5002dd2f 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2440,6 +2440,78 @@ abstract class AppLocalizations { /// **'Discard'** String get avatarSaveChangesDiscard; + /// No description provided for @customAvatarSvgTab. + /// + /// In en, this message translates to: + /// **'Public avatar'** + String get customAvatarSvgTab; + + /// No description provided for @customAvatarCustomize. + /// + /// In en, this message translates to: + /// **'Customize'** + String get customAvatarCustomize; + + /// No description provided for @customAvatarPhotoTab. + /// + /// In en, this message translates to: + /// **'Personal avatar'** + String get customAvatarPhotoTab; + + /// No description provided for @customAvatarExplanation. + /// + /// In en, this message translates to: + /// **'Replaces your public avatar for the selected audience.'** + String get customAvatarExplanation; + + /// No description provided for @customAvatarCreate. + /// + /// In en, this message translates to: + /// **'Choose photo'** + String get customAvatarCreate; + + /// No description provided for @customAvatarReplace. + /// + /// In en, this message translates to: + /// **'Change photo'** + String get customAvatarReplace; + + /// No description provided for @customAvatarRemove. + /// + /// In en, this message translates to: + /// **'Remove'** + String get customAvatarRemove; + + /// No description provided for @customAvatarAudience. + /// + /// In en, this message translates to: + /// **'Who can see the photo avatar?'** + String get customAvatarAudience; + + /// No description provided for @customAvatarAcceptedOnly. + /// + /// In en, this message translates to: + /// **'Contacts only'** + String get customAvatarAcceptedOnly; + + /// No description provided for @customAvatarEveryone. + /// + /// In en, this message translates to: + /// **'Everyone'** + String get customAvatarEveryone; + + /// No description provided for @customAvatarChooseSource. + /// + /// In en, this message translates to: + /// **'Choose avatar photo'** + String get customAvatarChooseSource; + + /// No description provided for @customAvatarCreateFailed. + /// + /// In en, this message translates to: + /// **'The photo avatar could not be created.'** + String get customAvatarCreateFailed; + /// No description provided for @inProcess. /// /// In en, this message translates to: diff --git a/lib/src/localization/generated/app_localizations_ar.dart b/lib/src/localization/generated/app_localizations_ar.dart index fa1f32da..7ea5531f 100644 --- a/lib/src/localization/generated/app_localizations_ar.dart +++ b/lib/src/localization/generated/app_localizations_ar.dart @@ -1398,6 +1398,43 @@ class AppLocalizationsAr extends AppLocalizations { @override String get avatarSaveChangesDiscard => 'تجاهل'; + @override + String get customAvatarSvgTab => 'الصورة الرمزية العامة'; + + @override + String get customAvatarCustomize => 'تخصيص'; + + @override + String get customAvatarPhotoTab => 'الصورة الرمزية الشخصية'; + + @override + String get customAvatarExplanation => + 'تحل محل صورتك الرمزية العامة لدى الجمهور المحدد.'; + + @override + String get customAvatarCreate => 'اختيار صورة'; + + @override + String get customAvatarReplace => 'تغيير الصورة'; + + @override + String get customAvatarRemove => 'إزالة'; + + @override + String get customAvatarAudience => 'من يمكنه رؤية الصورة الرمزية؟'; + + @override + String get customAvatarAcceptedOnly => 'جهات الاتصال فقط'; + + @override + String get customAvatarEveryone => 'الجميع'; + + @override + String get customAvatarChooseSource => 'اختر صورة رمزية'; + + @override + String get customAvatarCreateFailed => 'تعذر إنشاء الصورة الرمزية.'; + @override String get inProcess => 'قيد المعالجة'; diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 8064ad3c..3db53f71 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1387,6 +1387,44 @@ class AppLocalizationsDe extends AppLocalizations { @override String get avatarSaveChangesDiscard => 'Verwerfen'; + @override + String get customAvatarSvgTab => 'Öffentlicher Avatar'; + + @override + String get customAvatarCustomize => 'Anpassen'; + + @override + String get customAvatarPhotoTab => 'Persönlicher Avatar'; + + @override + String get customAvatarExplanation => + 'Ersetzt deinen öffentlichen Avatar für den ausgewählten Personenkreis.'; + + @override + String get customAvatarCreate => 'Foto auswählen'; + + @override + String get customAvatarReplace => 'Foto ändern'; + + @override + String get customAvatarRemove => 'Entfernen'; + + @override + String get customAvatarAudience => 'Wer darf den Foto-Avatar sehen?'; + + @override + String get customAvatarAcceptedOnly => 'Nur Kontakte'; + + @override + String get customAvatarEveryone => 'Alle'; + + @override + String get customAvatarChooseSource => 'Avatar-Foto auswählen'; + + @override + String get customAvatarCreateFailed => + 'Der Foto-Avatar konnte nicht erstellt werden.'; + @override String get inProcess => 'Wird verarbeitet'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index fb15dd68..39128db9 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1379,6 +1379,44 @@ class AppLocalizationsEn extends AppLocalizations { @override String get avatarSaveChangesDiscard => 'Discard'; + @override + String get customAvatarSvgTab => 'Public avatar'; + + @override + String get customAvatarCustomize => 'Customize'; + + @override + String get customAvatarPhotoTab => 'Personal avatar'; + + @override + String get customAvatarExplanation => + 'Replaces your public avatar for the selected audience.'; + + @override + String get customAvatarCreate => 'Choose photo'; + + @override + String get customAvatarReplace => 'Change photo'; + + @override + String get customAvatarRemove => 'Remove'; + + @override + String get customAvatarAudience => 'Who can see the photo avatar?'; + + @override + String get customAvatarAcceptedOnly => 'Contacts only'; + + @override + String get customAvatarEveryone => 'Everyone'; + + @override + String get customAvatarChooseSource => 'Choose avatar photo'; + + @override + String get customAvatarCreateFailed => + 'The photo avatar could not be created.'; + @override String get inProcess => 'In process'; diff --git a/lib/src/localization/translations/ar.arb b/lib/src/localization/translations/ar.arb index efd78879..041e3f33 100644 --- a/lib/src/localization/translations/ar.arb +++ b/lib/src/localization/translations/ar.arb @@ -390,6 +390,18 @@ "avatarSaveChanges": "هل تريد حفظ التغييرات؟", "avatarSaveChangesStore": "حفظ", "avatarSaveChangesDiscard": "تجاهل", + "customAvatarSvgTab": "الصورة الرمزية العامة", + "customAvatarCustomize": "تخصيص", + "customAvatarPhotoTab": "الصورة الرمزية الشخصية", + "customAvatarExplanation": "تحل محل صورتك الرمزية العامة لدى الجمهور المحدد.", + "customAvatarCreate": "اختيار صورة", + "customAvatarReplace": "تغيير الصورة", + "customAvatarRemove": "إزالة", + "customAvatarAudience": "من يمكنه رؤية الصورة الرمزية؟", + "customAvatarAcceptedOnly": "جهات الاتصال فقط", + "customAvatarEveryone": "الجميع", + "customAvatarChooseSource": "اختر صورة رمزية", + "customAvatarCreateFailed": "تعذر إنشاء الصورة الرمزية.", "inProcess": "قيد المعالجة", "draftMessage": "مسودة", "voiceMessageSlideToCancel": "اسحب للإلغاء", diff --git a/lib/src/localization/translations/de.arb b/lib/src/localization/translations/de.arb index 6f11543f..2ff097e6 100644 --- a/lib/src/localization/translations/de.arb +++ b/lib/src/localization/translations/de.arb @@ -455,6 +455,18 @@ "avatarSaveChanges": "Möchtest du die Änderungen speichern?", "avatarSaveChangesStore": "Speichern", "avatarSaveChangesDiscard": "Verwerfen", + "customAvatarSvgTab": "Öffentlicher Avatar", + "customAvatarCustomize": "Anpassen", + "customAvatarPhotoTab": "Persönlicher Avatar", + "customAvatarExplanation": "Ersetzt deinen öffentlichen Avatar für den ausgewählten Personenkreis.", + "customAvatarCreate": "Foto auswählen", + "customAvatarReplace": "Foto ändern", + "customAvatarRemove": "Entfernen", + "customAvatarAudience": "Wer darf den Foto-Avatar sehen?", + "customAvatarAcceptedOnly": "Nur Kontakte", + "customAvatarEveryone": "Alle", + "customAvatarChooseSource": "Avatar-Foto auswählen", + "customAvatarCreateFailed": "Der Foto-Avatar konnte nicht erstellt werden.", "inProcess": "Wird verarbeitet", "draftMessage": "Entwurf", "voiceMessageSlideToCancel": "Zum Abbrechen ziehen", diff --git a/lib/src/localization/translations/en.arb b/lib/src/localization/translations/en.arb index 675182fc..d6ccaab9 100644 --- a/lib/src/localization/translations/en.arb +++ b/lib/src/localization/translations/en.arb @@ -465,6 +465,18 @@ "avatarSaveChanges": "Would you like to save the changes?", "avatarSaveChangesStore": "Save", "avatarSaveChangesDiscard": "Discard", + "customAvatarSvgTab": "Public avatar", + "customAvatarCustomize": "Customize", + "customAvatarPhotoTab": "Personal avatar", + "customAvatarExplanation": "Replaces your public avatar for the selected audience.", + "customAvatarCreate": "Choose photo", + "customAvatarReplace": "Change photo", + "customAvatarRemove": "Remove", + "customAvatarAudience": "Who can see the photo avatar?", + "customAvatarAcceptedOnly": "Contacts only", + "customAvatarEveryone": "Everyone", + "customAvatarChooseSource": "Choose avatar photo", + "customAvatarCreateFailed": "The photo avatar could not be created.", "inProcess": "In process", "draftMessage": "Draft", "voiceMessageSlideToCancel": "Slide to cancel", diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index 6acc457d..620eee98 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -1174,6 +1174,8 @@ class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { $core.List<$core.int>? avatarSvgCompressed, $core.String? username, $core.String? displayName, + $core.bool? customAvatarRequested, + EncryptedContent_CustomAvatar? customAvatar, }) { final result = create(); if (type != null) result.type = type; @@ -1181,6 +1183,9 @@ class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { result.avatarSvgCompressed = avatarSvgCompressed; if (username != null) result.username = username; if (displayName != null) result.displayName = displayName; + if (customAvatarRequested != null) + result.customAvatarRequested = customAvatarRequested; + if (customAvatar != null) result.customAvatar = customAvatar; return result; } @@ -1202,6 +1207,10 @@ class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { 2, _omitFieldNames ? '' : 'avatarSvgCompressed', $pb.PbFieldType.OY) ..aOS(3, _omitFieldNames ? '' : 'username') ..aOS(4, _omitFieldNames ? '' : 'displayName') + ..aOB(5, _omitFieldNames ? '' : 'customAvatarRequested') + ..aOM( + 6, _omitFieldNames ? '' : 'customAvatar', + subBuilder: EncryptedContent_CustomAvatar.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1261,6 +1270,160 @@ class EncryptedContent_ContactUpdate extends $pb.GeneratedMessage { $core.bool hasDisplayName() => $_has(3); @$pb.TagNumber(4) void clearDisplayName() => $_clearField(4); + + /// Set on a REQUEST only by clients that understand CustomAvatar. + @$pb.TagNumber(5) + $core.bool get customAvatarRequested => $_getBF(4); + @$pb.TagNumber(5) + set customAvatarRequested($core.bool value) => $_setBool(4, value); + @$pb.TagNumber(5) + $core.bool hasCustomAvatarRequested() => $_has(4); + @$pb.TagNumber(5) + void clearCustomAvatarRequested() => $_clearField(5); + + /// Set on UPDATE only in direct response to such a request. + @$pb.TagNumber(6) + EncryptedContent_CustomAvatar get customAvatar => $_getN(5); + @$pb.TagNumber(6) + set customAvatar(EncryptedContent_CustomAvatar value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasCustomAvatar() => $_has(5); + @$pb.TagNumber(6) + void clearCustomAvatar() => $_clearField(6); + @$pb.TagNumber(6) + EncryptedContent_CustomAvatar ensureCustomAvatar() => $_ensure(5); +} + +class EncryptedContent_CustomAvatar extends $pb.GeneratedMessage { + factory EncryptedContent_CustomAvatar({ + $core.int? version, + $fixnum.Int64? publicationCounter, + EncryptedContent_CustomAvatar_State? state, + $core.List<$core.int>? webp, + $core.List<$core.int>? sha256, + $core.int? width, + $core.int? height, + }) { + final result = create(); + if (version != null) result.version = version; + if (publicationCounter != null) + result.publicationCounter = publicationCounter; + if (state != null) result.state = state; + if (webp != null) result.webp = webp; + if (sha256 != null) result.sha256 = sha256; + if (width != null) result.width = width; + if (height != null) result.height = height; + return result; + } + + EncryptedContent_CustomAvatar._(); + + factory EncryptedContent_CustomAvatar.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory EncryptedContent_CustomAvatar.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'EncryptedContent.CustomAvatar', + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'version', fieldType: $pb.PbFieldType.OU3) + ..aInt64(2, _omitFieldNames ? '' : 'publicationCounter') + ..aE(3, _omitFieldNames ? '' : 'state', + enumValues: EncryptedContent_CustomAvatar_State.values) + ..a<$core.List<$core.int>>( + 4, _omitFieldNames ? '' : 'webp', $pb.PbFieldType.OY) + ..a<$core.List<$core.int>>( + 5, _omitFieldNames ? '' : 'sha256', $pb.PbFieldType.OY) + ..aI(6, _omitFieldNames ? '' : 'width', fieldType: $pb.PbFieldType.OU3) + ..aI(7, _omitFieldNames ? '' : 'height', fieldType: $pb.PbFieldType.OU3) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EncryptedContent_CustomAvatar clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + EncryptedContent_CustomAvatar copyWith( + void Function(EncryptedContent_CustomAvatar) updates) => + super.copyWith( + (message) => updates(message as EncryptedContent_CustomAvatar)) + as EncryptedContent_CustomAvatar; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static EncryptedContent_CustomAvatar create() => + EncryptedContent_CustomAvatar._(); + @$core.override + EncryptedContent_CustomAvatar createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static EncryptedContent_CustomAvatar getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static EncryptedContent_CustomAvatar? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get version => $_getIZ(0); + @$pb.TagNumber(1) + set version($core.int value) => $_setUnsignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasVersion() => $_has(0); + @$pb.TagNumber(1) + void clearVersion() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get publicationCounter => $_getI64(1); + @$pb.TagNumber(2) + set publicationCounter($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasPublicationCounter() => $_has(1); + @$pb.TagNumber(2) + void clearPublicationCounter() => $_clearField(2); + + @$pb.TagNumber(3) + EncryptedContent_CustomAvatar_State get state => $_getN(2); + @$pb.TagNumber(3) + set state(EncryptedContent_CustomAvatar_State value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasState() => $_has(2); + @$pb.TagNumber(3) + void clearState() => $_clearField(3); + + @$pb.TagNumber(4) + $core.List<$core.int> get webp => $_getN(3); + @$pb.TagNumber(4) + set webp($core.List<$core.int> value) => $_setBytes(3, value); + @$pb.TagNumber(4) + $core.bool hasWebp() => $_has(3); + @$pb.TagNumber(4) + void clearWebp() => $_clearField(4); + + @$pb.TagNumber(5) + $core.List<$core.int> get sha256 => $_getN(4); + @$pb.TagNumber(5) + set sha256($core.List<$core.int> value) => $_setBytes(4, value); + @$pb.TagNumber(5) + $core.bool hasSha256() => $_has(4); + @$pb.TagNumber(5) + void clearSha256() => $_clearField(5); + + @$pb.TagNumber(6) + $core.int get width => $_getIZ(5); + @$pb.TagNumber(6) + set width($core.int value) => $_setUnsignedInt32(5, value); + @$pb.TagNumber(6) + $core.bool hasWidth() => $_has(5); + @$pb.TagNumber(6) + void clearWidth() => $_clearField(6); + + @$pb.TagNumber(7) + $core.int get height => $_getIZ(6); + @$pb.TagNumber(7) + set height($core.int value) => $_setUnsignedInt32(6, value); + @$pb.TagNumber(7) + $core.bool hasHeight() => $_has(6); + @$pb.TagNumber(7) + void clearHeight() => $_clearField(7); } class EncryptedContent_FlameSync extends $pb.GeneratedMessage { @@ -1775,6 +1938,8 @@ class EncryptedContent extends $pb.GeneratedMessage { $core.bool? widgetSharingAllowed, EncryptedContent_Story? story, $fixnum.Int64? senderTwonlyScore, + $fixnum.Int64? senderCustomAvatarCounter, + $core.int? customAvatarProtocolVersion, }) { final result = create(); if (groupId != null) result.groupId = groupId; @@ -1816,6 +1981,10 @@ class EncryptedContent extends $pb.GeneratedMessage { result.widgetSharingAllowed = widgetSharingAllowed; if (story != null) result.story = story; if (senderTwonlyScore != null) result.senderTwonlyScore = senderTwonlyScore; + if (senderCustomAvatarCounter != null) + result.senderCustomAvatarCounter = senderCustomAvatarCounter; + if (customAvatarProtocolVersion != null) + result.customAvatarProtocolVersion = customAvatarProtocolVersion; return result; } @@ -1896,6 +2065,9 @@ class EncryptedContent extends $pb.GeneratedMessage { ..aOM(30, _omitFieldNames ? '' : 'story', subBuilder: EncryptedContent_Story.create) ..aInt64(31, _omitFieldNames ? '' : 'senderTwonlyScore') + ..aInt64(32, _omitFieldNames ? '' : 'senderCustomAvatarCounter') + ..aI(33, _omitFieldNames ? '' : 'customAvatarProtocolVersion', + fieldType: $pb.PbFieldType.OU3) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -2226,6 +2398,7 @@ class EncryptedContent extends $pb.GeneratedMessage { @$pb.TagNumber(30) EncryptedContent_Story ensureStory() => $_ensure(26); + /// Sum of all exchanged media, omitted when profile sharing is disabled. @$pb.TagNumber(31) $fixnum.Int64 get senderTwonlyScore => $_getI64(27); @$pb.TagNumber(31) @@ -2234,6 +2407,27 @@ class EncryptedContent extends $pb.GeneratedMessage { $core.bool hasSenderTwonlyScore() => $_has(27); @$pb.TagNumber(31) void clearSenderTwonlyScore() => $_clearField(31); + + /// Per-recipient version of the custom photo-avatar state. Receivers request + /// the payload only when this advances; the image is never broadcast. + @$pb.TagNumber(32) + $fixnum.Int64 get senderCustomAvatarCounter => $_getI64(28); + @$pb.TagNumber(32) + set senderCustomAvatarCounter($fixnum.Int64 value) => $_setInt64(28, value); + @$pb.TagNumber(32) + $core.bool hasSenderCustomAvatarCounter() => $_has(28); + @$pb.TagNumber(32) + void clearSenderCustomAvatarCounter() => $_clearField(32); + + @$pb.TagNumber(33) + $core.int get customAvatarProtocolVersion => $_getIZ(29); + @$pb.TagNumber(33) + set customAvatarProtocolVersion($core.int value) => + $_setUnsignedInt32(29, value); + @$pb.TagNumber(33) + $core.bool hasCustomAvatarProtocolVersion() => $_has(29); + @$pb.TagNumber(33) + void clearCustomAvatarProtocolVersion() => $_clearField(33); } const $core.bool _omitFieldNames = diff --git a/lib/src/model/protobuf/client/generated/messages.pbenum.dart b/lib/src/model/protobuf/client/generated/messages.pbenum.dart index d98c74c6..71a5fc1e 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbenum.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbenum.dart @@ -169,5 +169,26 @@ class EncryptedContent_ContactUpdate_Type extends $pb.ProtobufEnum { const EncryptedContent_ContactUpdate_Type._(super.value, super.name); } +class EncryptedContent_CustomAvatar_State extends $pb.ProtobufEnum { + static const EncryptedContent_CustomAvatar_State SVG_ONLY = + EncryptedContent_CustomAvatar_State._( + 0, _omitEnumNames ? '' : 'SVG_ONLY'); + static const EncryptedContent_CustomAvatar_State PHOTO = + EncryptedContent_CustomAvatar_State._(1, _omitEnumNames ? '' : 'PHOTO'); + + static const $core.List values = + [ + SVG_ONLY, + PHOTO, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 1); + static EncryptedContent_CustomAvatar_State? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const EncryptedContent_CustomAvatar_State._(super.value, super.name); +} + const $core.bool _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index d71b1d35..9924c8ca 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -82,13 +82,31 @@ const EncryptedContent$json = { '10': 'senderTwonlyScore', '17': true }, + { + '1': 'sender_custom_avatar_counter', + '3': 32, + '4': 1, + '5': 3, + '9': 7, + '10': 'senderCustomAvatarCounter', + '17': true + }, + { + '1': 'custom_avatar_protocol_version', + '3': 33, + '4': 1, + '5': 13, + '9': 8, + '10': 'customAvatarProtocolVersion', + '17': true + }, { '1': 'message_update', '3': 5, '4': 1, '5': 11, '6': '.EncryptedContent.MessageUpdate', - '9': 7, + '9': 9, '10': 'messageUpdate', '17': true }, @@ -98,7 +116,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.Media', - '9': 8, + '9': 10, '10': 'media', '17': true }, @@ -108,7 +126,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.MediaUpdate', - '9': 9, + '9': 11, '10': 'mediaUpdate', '17': true }, @@ -118,7 +136,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.ContactUpdate', - '9': 10, + '9': 12, '10': 'contactUpdate', '17': true }, @@ -128,7 +146,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.ContactRequest', - '9': 11, + '9': 13, '10': 'contactRequest', '17': true }, @@ -138,7 +156,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.FlameSync', - '9': 12, + '9': 14, '10': 'flameSync', '17': true }, @@ -148,7 +166,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.Reaction', - '9': 13, + '9': 15, '10': 'reaction', '17': true }, @@ -158,7 +176,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.TextMessage', - '9': 14, + '9': 16, '10': 'textMessage', '17': true }, @@ -168,7 +186,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.GroupCreate', - '9': 15, + '9': 17, '10': 'groupCreate', '17': true }, @@ -178,7 +196,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.GroupJoin', - '9': 16, + '9': 18, '10': 'groupJoin', '17': true }, @@ -188,7 +206,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.GroupUpdate', - '9': 17, + '9': 19, '10': 'groupUpdate', '17': true }, @@ -198,7 +216,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.ResendGroupPublicKey', - '9': 18, + '9': 20, '10': 'resendGroupPublicKey', '17': true }, @@ -208,7 +226,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.ErrorMessages', - '9': 19, + '9': 21, '10': 'errorMessages', '17': true }, @@ -218,7 +236,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.AdditionalDataMessage', - '9': 20, + '9': 22, '10': 'additionalDataMessage', '17': true }, @@ -228,7 +246,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.TypingIndicator', - '9': 21, + '9': 23, '10': 'typingIndicator', '17': true }, @@ -238,7 +256,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.UserDiscoveryRequest', - '9': 22, + '9': 24, '10': 'userDiscoveryRequest', '17': true }, @@ -248,7 +266,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.UserDiscoveryUpdate', - '9': 23, + '9': 25, '10': 'userDiscoveryUpdate', '17': true }, @@ -258,7 +276,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.KeyVerificationProof', - '9': 24, + '9': 26, '10': 'keyVerificationProof', '17': true }, @@ -268,7 +286,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.PasswordLessRecovery', - '9': 25, + '9': 27, '10': 'passwordlessRecovery', '17': true }, @@ -278,7 +296,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.PasswordLessRecoveryHeartbeat', - '9': 26, + '9': 28, '10': 'passwordlessRecoveryHeartbeat', '17': true }, @@ -288,7 +306,7 @@ const EncryptedContent$json = { '4': 1, '5': 11, '6': '.EncryptedContent.Story', - '9': 27, + '9': 29, '10': 'story', '17': true }, @@ -308,6 +326,7 @@ const EncryptedContent$json = { EncryptedContent_MediaUpdate$json, EncryptedContent_ContactRequest$json, EncryptedContent_ContactUpdate$json, + EncryptedContent_CustomAvatar$json, EncryptedContent_FlameSync$json, EncryptedContent_TypingIndicator$json, EncryptedContent_UserDiscoveryRequest$json, @@ -324,6 +343,8 @@ const EncryptedContent$json = { {'1': '_ask_for_friend_promotions'}, {'1': '_widget_sharing_allowed'}, {'1': '_sender_twonly_score'}, + {'1': '_sender_custom_avatar_counter'}, + {'1': '_custom_avatar_protocol_version'}, {'1': '_message_update'}, {'1': '_media'}, {'1': '_media_update'}, @@ -799,12 +820,33 @@ const EncryptedContent_ContactUpdate$json = { '10': 'displayName', '17': true }, + { + '1': 'custom_avatar_requested', + '3': 5, + '4': 1, + '5': 8, + '9': 3, + '10': 'customAvatarRequested', + '17': true + }, + { + '1': 'custom_avatar', + '3': 6, + '4': 1, + '5': 11, + '6': '.EncryptedContent.CustomAvatar', + '9': 4, + '10': 'customAvatar', + '17': true + }, ], '4': [EncryptedContent_ContactUpdate_Type$json], '8': [ {'1': '_avatar_svg_compressed'}, {'1': '_username'}, {'1': '_display_name'}, + {'1': '_custom_avatar_requested'}, + {'1': '_custom_avatar'}, ], }; @@ -817,6 +859,65 @@ const EncryptedContent_ContactUpdate_Type$json = { ], }; +@$core.Deprecated('Use encryptedContentDescriptor instead') +const EncryptedContent_CustomAvatar$json = { + '1': 'CustomAvatar', + '2': [ + {'1': 'version', '3': 1, '4': 1, '5': 13, '10': 'version'}, + { + '1': 'publication_counter', + '3': 2, + '4': 1, + '5': 3, + '10': 'publicationCounter' + }, + { + '1': 'state', + '3': 3, + '4': 1, + '5': 14, + '6': '.EncryptedContent.CustomAvatar.State', + '10': 'state' + }, + {'1': 'webp', '3': 4, '4': 1, '5': 12, '9': 0, '10': 'webp', '17': true}, + { + '1': 'sha256', + '3': 5, + '4': 1, + '5': 12, + '9': 1, + '10': 'sha256', + '17': true + }, + {'1': 'width', '3': 6, '4': 1, '5': 13, '9': 2, '10': 'width', '17': true}, + { + '1': 'height', + '3': 7, + '4': 1, + '5': 13, + '9': 3, + '10': 'height', + '17': true + }, + ], + '4': [EncryptedContent_CustomAvatar_State$json], + '8': [ + {'1': '_webp'}, + {'1': '_sha256'}, + {'1': '_width'}, + {'1': '_height'}, + ], +}; + +@$core.Deprecated('Use encryptedContentDescriptor instead') +const EncryptedContent_CustomAvatar_State$json = { + '1': 'State', + '2': [ + {'1': 'SVG_ONLY', '2': 0}, + {'1': 'PHOTO', '2': 1}, + ], +}; + @$core.Deprecated('Use encryptedContentDescriptor instead') const EncryptedContent_FlameSync$json = { '1': 'FlameSync', @@ -905,115 +1006,129 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( 'AQESPgoZYXNrX2Zvcl9mcmllbmRfcHJvbW90aW9ucxgZIAEoCEgEUhZhc2tGb3JGcmllbmRQcm' '9tb3Rpb25ziAEBEjkKFndpZGdldF9zaGFyaW5nX2FsbG93ZWQYHSABKAhIBVIUd2lkZ2V0U2hh' 'cmluZ0FsbG93ZWSIAQESMwoTc2VuZGVyX3R3b25seV9zY29yZRgfIAEoA0gGUhFzZW5kZXJUd2' - '9ubHlTY29yZYgBARJLCg5tZXNzYWdlX3VwZGF0ZRgFIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQu' - 'TWVzc2FnZVVwZGF0ZUgHUg1tZXNzYWdlVXBkYXRliAEBEjIKBW1lZGlhGAYgASgLMhcuRW5jcn' - 'lwdGVkQ29udGVudC5NZWRpYUgIUgVtZWRpYYgBARJFCgxtZWRpYV91cGRhdGUYByABKAsyHS5F' - 'bmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlSAlSC21lZGlhVXBkYXRliAEBEksKDmNvbnRhY3' - 'RfdXBkYXRlGAggASgLMh8uRW5jcnlwdGVkQ29udGVudC5Db250YWN0VXBkYXRlSApSDWNvbnRh' - 'Y3RVcGRhdGWIAQESTgoPY29udGFjdF9yZXF1ZXN0GAkgASgLMiAuRW5jcnlwdGVkQ29udGVudC' - '5Db250YWN0UmVxdWVzdEgLUg5jb250YWN0UmVxdWVzdIgBARI/CgpmbGFtZV9zeW5jGAogASgL' - 'MhsuRW5jcnlwdGVkQ29udGVudC5GbGFtZVN5bmNIDFIJZmxhbWVTeW5jiAEBEjsKCHJlYWN0aW' - '9uGAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgNUghyZWFjdGlvbogBARJFCgx0' - 'ZXh0X21lc3NhZ2UYDSABKAsyHS5FbmNyeXB0ZWRDb250ZW50LlRleHRNZXNzYWdlSA5SC3RleH' - 'RNZXNzYWdliAEBEkUKDGdyb3VwX2NyZWF0ZRgOIAEoCzIdLkVuY3J5cHRlZENvbnRlbnQuR3Jv' - 'dXBDcmVhdGVID1ILZ3JvdXBDcmVhdGWIAQESPwoKZ3JvdXBfam9pbhgPIAEoCzIbLkVuY3J5cH' - 'RlZENvbnRlbnQuR3JvdXBKb2luSBBSCWdyb3VwSm9pbogBARJFCgxncm91cF91cGRhdGUYECAB' - 'KAsyHS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSBFSC2dyb3VwVXBkYXRliAEBEmIKF3' - 'Jlc2VuZF9ncm91cF9wdWJsaWNfa2V5GBEgASgLMiYuRW5jcnlwdGVkQ29udGVudC5SZXNlbmRH' - 'cm91cFB1YmxpY0tleUgSUhRyZXNlbmRHcm91cFB1YmxpY0tleYgBARJLCg5lcnJvcl9tZXNzYW' - 'dlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlc0gTUg1lcnJvck1lc3Nh' - 'Z2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgASgLMicuRW5jcnlwdGVkQ29udG' - 'VudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIFFIVYWRkaXRpb25hbERhdGFNZXNzYWdliAEBElEK' - 'EHR5cGluZ19pbmRpY2F0b3IYFCABKAsyIS5FbmNyeXB0ZWRDb250ZW50LlR5cGluZ0luZGljYX' - 'RvckgVUg90eXBpbmdJbmRpY2F0b3KIAQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEo' - 'CzImLkVuY3J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVJlcXVlc3RIFlIUdXNlckRpc2Nvdm' - 'VyeVJlcXVlc3SIAQESXgoVdXNlcl9kaXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVk' - 'Q29udGVudC5Vc2VyRGlzY292ZXJ5VXBkYXRlSBdSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQESYQ' - 'oWa2V5X3ZlcmlmaWNhdGlvbl9wcm9vZhgYIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuS2V5VmVy' - 'aWZpY2F0aW9uUHJvb2ZIGFIUa2V5VmVyaWZpY2F0aW9uUHJvb2aIAQESYAoVcGFzc3dvcmRsZX' - 'NzX3JlY292ZXJ5GBogASgLMiYuRW5jcnlwdGVkQ29udGVudC5QYXNzd29yZExlc3NSZWNvdmVy' - 'eUgZUhRwYXNzd29yZGxlc3NSZWNvdmVyeYgBARJ8Ch9wYXNzd29yZGxlc3NfcmVjb3ZlcnlfaG' - 'VhcnRiZWF0GBsgASgLMi8uRW5jcnlwdGVkQ29udGVudC5QYXNzd29yZExlc3NSZWNvdmVyeUhl' - 'YXJ0YmVhdEgaUh1wYXNzd29yZGxlc3NSZWNvdmVyeUhlYXJ0YmVhdIgBARIyCgVzdG9yeRgeIA' - 'EoCzIXLkVuY3J5cHRlZENvbnRlbnQuU3RvcnlIG1IFc3RvcnmIAQEalgIKDUVycm9yTWVzc2Fn' - 'ZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeXBlUg' - 'R0eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCKcAQoE' - 'VHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1' - 'RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVTU0FHRV9UWVBFEAISFwoTU0VTU0lPTl9PVVRfT0Zf' - 'U1lOQxADEiMKH0dST1VQX05PVF9GT1VORF9PUl9OT1RfQV9NRU1CRVIQBBqHAQoLR3JvdXBDcm' - 'VhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91cF9wdWJsaWNfa2V5GAQg' - 'ASgMUg5ncm91cFB1YmxpY0tleRIiCgpncm91cF9uYW1lGAUgASgJSABSCWdyb3VwTmFtZYgBAU' - 'INCgtfZ3JvdXBfbmFtZRo1CglHcm91cEpvaW4SKAoQZ3JvdXBfcHVibGljX2tleRgBIAEoDFIO' - 'Z3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkayAIKC0dyb3VwVXBkYXRlEi' - 'oKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMwoTYWZmZWN0ZWRf' - 'Y29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZIgBARIpCg5uZXdfZ3JvdXBfbm' - 'FtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2RlbGV0ZV9tZXNzYWdlc19hZnRl' - 'cl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb2' - '5kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbmV3X2dyb3VwX25hbWVCKQonX25l' - 'd19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGogCCgtUZXh0TWVzc2FnZRIqCh' - 'FzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlS' - 'BHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZB' - 'gEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgF' - 'IAEoDEgBUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQFCEwoRX3F1b3RlX21lc3NhZ2VfaWRCGg' - 'oYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGuYBChVBZGRpdGlvbmFsRGF0YU1lc3NhZ2USKgoR' - 'c2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIcCgl0aW1lc3RhbXAYAi' - 'ABKANSCXRpbWVzdGFtcBISCgR0eXBlGAMgASgJUgR0eXBlEjsKF2FkZGl0aW9uYWxfbWVzc2Fn' - 'ZV9kYXRhGAQgASgMSABSFWFkZGl0aW9uYWxNZXNzYWdlRGF0YYgBARIWCgZoaWRkZW4YBSABKA' - 'hSBmhpZGRlbkIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEaZAoIUmVhY3Rpb24SKgoRdGFy' - 'Z2V0X21lc3NhZ2VfaWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW' - '1vamkSFgoGcmVtb3ZlGAMgASgIUgZyZW1vdmUavgIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgB' - 'IAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi8KEXNlbm' - 'Rlcl9tZXNzYWdlX2lkGAIgASgJSABSD3NlbmRlck1lc3NhZ2VJZIgBARI9ChttdWx0aXBsZV90' - 'YXJnZXRfbWVzc2FnZV9pZHMYAyADKAlSGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxIXCgR0ZX' - 'h0GAQgASgJSAFSBHRleHSIAQESHAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlw' - 'ZRIKCgZERUxFVEUQABINCglFRElUX1RFWFQQARIKCgZPUEVORUQQAkIUChJfc2VuZGVyX21lc3' - 'NhZ2VfaWRCBwoFX3RleHQauwYKBU1lZGlhEioKEXNlbmRlcl9tZXNzYWdlX2lkGAEgASgJUg9z' - 'ZW5kZXJNZXNzYWdlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVH' - 'lwZVIEdHlwZRJGCh1kaXNwbGF5X2xpbWl0X2luX21pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNw' - 'bGF5TGltaXRJbk1pbGxpc2Vjb25kc4gBARI3ChdyZXF1aXJlc19hdXRoZW50aWNhdGlvbhgEIA' - 'EoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVzdGFt' - 'cBItChBxdW90ZV9tZXNzYWdlX2lkGAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEioKDmRvd2' - '5sb2FkX3Rva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKgoOZW5jcnlwdGlvbl9rZXkY' - 'CCABKAxIA1INZW5jcnlwdGlvbktleYgBARIqCg5lbmNyeXB0aW9uX21hYxgJIAEoDEgEUg1lbm' - 'NyeXB0aW9uTWFjiAEBEi4KEGVuY3J5cHRpb25fbm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5v' - 'bmNliAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZX' - 'NzYWdlRGF0YYgBARIkCgt3aWRnZXRfb25seRgMIAEoCEgHUgp3aWRnZXRPbmx5iAEBIj4KBFR5' - 'cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU' - '8QBEIgCh5fZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY29uZHNCEwoRX3F1b3RlX21lc3NhZ2Vf' - 'aWRCEQoPX2Rvd25sb2FkX3Rva2VuQhEKD19lbmNyeXB0aW9uX2tleUIRCg9fZW5jcnlwdGlvbl' - '9tYWNCEwoRX2VuY3J5cHRpb25fbm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhQg4K' - 'DF93aWRnZXRfb25seRpOCgVTdG9yeRItCgVtZWRpYRgBIAEoCzIXLkVuY3J5cHRlZENvbnRlbn' - 'QuTWVkaWFSBW1lZGlhEhYKBm5vdGlmeRgCIAEoCFIGbm90aWZ5GqkBCgtNZWRpYVVwZGF0ZRI2' - 'CgR0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEi' - 'oKEXRhcmdldF9tZXNzYWdlX2lkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghS' - 'RU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0Um' - 'VxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5U' - 'eXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGq' - 'QCCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRh' - 'Y3RVcGRhdGUuVHlwZVIEdHlwZRI3ChVhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAFITYX' - 'ZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgBARIm' - 'CgxkaXNwbGF5X25hbWUYBCABKAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRV' - 'NUEAASCgoGVVBEQVRFEAFCGAoWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5hbWVC' - 'DwoNX2Rpc3BsYXlfbmFtZRqvAQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDG' - 'ZsYW1lQ291bnRlchI5ChlsYXN0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0Rmxh' - 'bWVDb3VudGVyQ2hhbmdlEh8KC2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcm' - 'NlX3VwZGF0ZRgEIAEoCFILZm9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5' - 'cGluZxgBIAEoCFIIaXNUeXBpbmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFF' - 'VzZXJEaXNjb3ZlcnlSZXF1ZXN0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZl' - 'cnNpb24aMQoTVXNlckRpc2NvdmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZX' - 'MaPQoUS2V5VmVyaWZpY2F0aW9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1' - 'bGF0ZWRNYWMamwEKFFBhc3N3b3JkTGVzc1JlY292ZXJ5EjUKE3JlY292ZXJ5U2VjcmV0U2hhcm' - 'UYASABKAxIAFITcmVjb3ZlcnlTZWNyZXRTaGFyZYgBARIWCgZkZWxldGUYAiABKAhSBmRlbGV0' - 'ZRIcCgl0aHJlc2hvbGQYAyABKANSCXRocmVzaG9sZEIWChRfcmVjb3ZlcnlTZWNyZXRTaGFyZR' - 'ozCh1QYXNzd29yZExlc3NSZWNvdmVyeUhlYXJ0YmVhdBISCgRoYXNoGAEgASgMUgRoYXNoQgsK' - 'CV9ncm91cF9pZEIRCg9faXNfZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm9maWxlX2NvdW50ZX' - 'JCIAoeX3NlbmRlcl91c2VyX2Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tfZm9yX2ZyaWVuZF9w' - 'cm9tb3Rpb25zQhkKF193aWRnZXRfc2hhcmluZ19hbGxvd2VkQhYKFF9zZW5kZXJfdHdvbmx5X3' - 'Njb3JlQhEKD19tZXNzYWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX21lZGlhX3VwZGF0ZUIRCg9f' - 'Y29udGFjdF91cGRhdGVCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtfZmxhbWVfc3luY0ILCglfcm' - 'VhY3Rpb25CDwoNX3RleHRfbWVzc2FnZUIPCg1fZ3JvdXBfY3JlYXRlQg0KC19ncm91cF9qb2lu' - 'Qg8KDV9ncm91cF91cGRhdGVCGgoYX3Jlc2VuZF9ncm91cF9wdWJsaWNfa2V5QhEKD19lcnJvcl' - '9tZXNzYWdlc0IaChhfYWRkaXRpb25hbF9kYXRhX21lc3NhZ2VCEwoRX3R5cGluZ19pbmRpY2F0' - 'b3JCGQoXX3VzZXJfZGlzY292ZXJ5X3JlcXVlc3RCGAoWX3VzZXJfZGlzY292ZXJ5X3VwZGF0ZU' - 'IZChdfa2V5X3ZlcmlmaWNhdGlvbl9wcm9vZkIYChZfcGFzc3dvcmRsZXNzX3JlY292ZXJ5QiIK' - 'IF9wYXNzd29yZGxlc3NfcmVjb3ZlcnlfaGVhcnRiZWF0QggKBl9zdG9yeUoECBwQHQ=='); + '9ubHlTY29yZYgBARJEChxzZW5kZXJfY3VzdG9tX2F2YXRhcl9jb3VudGVyGCAgASgDSAdSGXNl' + 'bmRlckN1c3RvbUF2YXRhckNvdW50ZXKIAQESSAoeY3VzdG9tX2F2YXRhcl9wcm90b2NvbF92ZX' + 'JzaW9uGCEgASgNSAhSG2N1c3RvbUF2YXRhclByb3RvY29sVmVyc2lvbogBARJLCg5tZXNzYWdl' + 'X3VwZGF0ZRgFIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZUgJUg1tZXNzYW' + 'dlVXBkYXRliAEBEjIKBW1lZGlhGAYgASgLMhcuRW5jcnlwdGVkQ29udGVudC5NZWRpYUgKUgVt' + 'ZWRpYYgBARJFCgxtZWRpYV91cGRhdGUYByABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVX' + 'BkYXRlSAtSC21lZGlhVXBkYXRliAEBEksKDmNvbnRhY3RfdXBkYXRlGAggASgLMh8uRW5jcnlw' + 'dGVkQ29udGVudC5Db250YWN0VXBkYXRlSAxSDWNvbnRhY3RVcGRhdGWIAQESTgoPY29udGFjdF' + '9yZXF1ZXN0GAkgASgLMiAuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdEgNUg5jb250' + 'YWN0UmVxdWVzdIgBARI/CgpmbGFtZV9zeW5jGAogASgLMhsuRW5jcnlwdGVkQ29udGVudC5GbG' + 'FtZVN5bmNIDlIJZmxhbWVTeW5jiAEBEjsKCHJlYWN0aW9uGAwgASgLMhouRW5jcnlwdGVkQ29u' + 'dGVudC5SZWFjdGlvbkgPUghyZWFjdGlvbogBARJFCgx0ZXh0X21lc3NhZ2UYDSABKAsyHS5Fbm' + 'NyeXB0ZWRDb250ZW50LlRleHRNZXNzYWdlSBBSC3RleHRNZXNzYWdliAEBEkUKDGdyb3VwX2Ny' + 'ZWF0ZRgOIAEoCzIdLkVuY3J5cHRlZENvbnRlbnQuR3JvdXBDcmVhdGVIEVILZ3JvdXBDcmVhdG' + 'WIAQESPwoKZ3JvdXBfam9pbhgPIAEoCzIbLkVuY3J5cHRlZENvbnRlbnQuR3JvdXBKb2luSBJS' + 'CWdyb3VwSm9pbogBARJFCgxncm91cF91cGRhdGUYECABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lk' + 'dyb3VwVXBkYXRlSBNSC2dyb3VwVXBkYXRliAEBEmIKF3Jlc2VuZF9ncm91cF9wdWJsaWNfa2V5' + 'GBEgASgLMiYuRW5jcnlwdGVkQ29udGVudC5SZXNlbmRHcm91cFB1YmxpY0tleUgUUhRyZXNlbm' + 'RHcm91cFB1YmxpY0tleYgBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENv' + 'bnRlbnQuRXJyb3JNZXNzYWdlc0gVUg1lcnJvck1lc3NhZ2VziAEBEmQKF2FkZGl0aW9uYWxfZG' + 'F0YV9tZXNzYWdlGBMgASgLMicuRW5jcnlwdGVkQ29udGVudC5BZGRpdGlvbmFsRGF0YU1lc3Nh' + 'Z2VIFlIVYWRkaXRpb25hbERhdGFNZXNzYWdliAEBElEKEHR5cGluZ19pbmRpY2F0b3IYFCABKA' + 'syIS5FbmNyeXB0ZWRDb250ZW50LlR5cGluZ0luZGljYXRvckgXUg90eXBpbmdJbmRpY2F0b3KI' + 'AQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuVX' + 'NlckRpc2NvdmVyeVJlcXVlc3RIGFIUdXNlckRpc2NvdmVyeVJlcXVlc3SIAQESXgoVdXNlcl9k' + 'aXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVkQ29udGVudC5Vc2VyRGlzY292ZXJ5VX' + 'BkYXRlSBlSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQESYQoWa2V5X3ZlcmlmaWNhdGlvbl9wcm9v' + 'ZhgYIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuS2V5VmVyaWZpY2F0aW9uUHJvb2ZIGlIUa2V5Vm' + 'VyaWZpY2F0aW9uUHJvb2aIAQESYAoVcGFzc3dvcmRsZXNzX3JlY292ZXJ5GBogASgLMiYuRW5j' + 'cnlwdGVkQ29udGVudC5QYXNzd29yZExlc3NSZWNvdmVyeUgbUhRwYXNzd29yZGxlc3NSZWNvdm' + 'VyeYgBARJ8Ch9wYXNzd29yZGxlc3NfcmVjb3ZlcnlfaGVhcnRiZWF0GBsgASgLMi8uRW5jcnlw' + 'dGVkQ29udGVudC5QYXNzd29yZExlc3NSZWNvdmVyeUhlYXJ0YmVhdEgcUh1wYXNzd29yZGxlc3' + 'NSZWNvdmVyeUhlYXJ0YmVhdIgBARIyCgVzdG9yeRgeIAEoCzIXLkVuY3J5cHRlZENvbnRlbnQu' + 'U3RvcnlIHVIFc3RvcnmIAQEalgIKDUVycm9yTWVzc2FnZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3' + 'J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeXBlUgR0eXBlEiwKEnJlbGF0ZWRfcmVjZWlw' + 'dF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCKcAQoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU' + '5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05f' + 'TUVTU0FHRV9UWVBFEAISFwoTU0VTU0lPTl9PVVRfT0ZfU1lOQxADEiMKH0dST1VQX05PVF9GT1' + 'VORF9PUl9OT1RfQV9NRU1CRVIQBBqHAQoLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgM' + 'UghzdGF0ZUtleRIoChBncm91cF9wdWJsaWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRIiCg' + 'pncm91cF9uYW1lGAUgASgJSABSCWdyb3VwTmFtZYgBAUINCgtfZ3JvdXBfbmFtZRo1CglHcm91' + 'cEpvaW4SKAoQZ3JvdXBfcHVibGljX2tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW' + '5kR3JvdXBQdWJsaWNLZXkayAIKC0dyb3VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEg' + 'ASgJUg9ncm91cEFjdGlvblR5cGUSMwoTYWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZm' + 'ZlY3RlZENvbnRhY3RJZIgBARIpCg5uZXdfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5h' + 'bWWIAQESVwombmV3X2RlbGV0ZV9tZXNzYWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAl' + 'IibmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29u' + 'dGFjdF9pZEIRCg9fbmV3X2dyb3VwX25hbWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZX' + 'JfbWlsbGlzZWNvbmRzGogCCgtUZXh0TWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEo' + 'CVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgAS' + 'gDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJ' + 'ZIgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgFIAEoDEgBUhVhZGRpdGlvbmFsTWVzc2' + 'FnZURhdGGIAQFCEwoRX3F1b3RlX21lc3NhZ2VfaWRCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9k' + 'YXRhGuYBChVBZGRpdGlvbmFsRGF0YU1lc3NhZ2USKgoRc2VuZGVyX21lc3NhZ2VfaWQYASABKA' + 'lSD3NlbmRlck1lc3NhZ2VJZBIcCgl0aW1lc3RhbXAYAiABKANSCXRpbWVzdGFtcBISCgR0eXBl' + 'GAMgASgJUgR0eXBlEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAQgASgMSABSFWFkZGl0aW' + '9uYWxNZXNzYWdlRGF0YYgBARIWCgZoaWRkZW4YBSABKAhSBmhpZGRlbkIaChhfYWRkaXRpb25h' + 'bF9tZXNzYWdlX2RhdGEaZAoIUmVhY3Rpb24SKgoRdGFyZ2V0X21lc3NhZ2VfaWQYASABKAlSD3' + 'RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1vamkSFgoGcmVtb3ZlGAMgASgIUgZy' + 'ZW1vdmUavgIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbn' + 'QuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi8KEXNlbmRlcl9tZXNzYWdlX2lkGAIgASgJSABS' + 'D3NlbmRlck1lc3NhZ2VJZIgBARI9ChttdWx0aXBsZV90YXJnZXRfbWVzc2FnZV9pZHMYAyADKA' + 'lSGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxIXCgR0ZXh0GAQgASgJSAFSBHRleHSIAQESHAoJ' + 'dGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlwZRIKCgZERUxFVEUQABINCglFRElUX1' + 'RFWFQQARIKCgZPUEVORUQQAkIUChJfc2VuZGVyX21lc3NhZ2VfaWRCBwoFX3RleHQauwYKBU1l' + 'ZGlhEioKEXNlbmRlcl9tZXNzYWdlX2lkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSMAoEdHlwZR' + 'gCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJGCh1kaXNwbGF5X2xp' + 'bWl0X2luX21pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kc4' + 'gBARI3ChdyZXF1aXJlc19hdXRoZW50aWNhdGlvbhgEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNh' + 'dGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVzdGFtcBItChBxdW90ZV9tZXNzYWdlX2lkGA' + 'YgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEioKDmRvd25sb2FkX3Rva2VuGAcgASgMSAJSDWRv' + 'd25sb2FkVG9rZW6IAQESKgoOZW5jcnlwdGlvbl9rZXkYCCABKAxIA1INZW5jcnlwdGlvbktleY' + 'gBARIqCg5lbmNyeXB0aW9uX21hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi4KEGVuY3J5' + 'cHRpb25fbm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5vbmNliAEBEjsKF2FkZGl0aW9uYWxfbW' + 'Vzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZXNzYWdlRGF0YYgBARIkCgt3aWRnZXRf' + 'b25seRgMIAEoCEgHUgp3aWRnZXRPbmx5iAEBIj4KBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTU' + 'FHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIgCh5fZGlzcGxheV9saW1pdF9p' + 'bl9taWxsaXNlY29uZHNCEwoRX3F1b3RlX21lc3NhZ2VfaWRCEQoPX2Rvd25sb2FkX3Rva2VuQh' + 'EKD19lbmNyeXB0aW9uX2tleUIRCg9fZW5jcnlwdGlvbl9tYWNCEwoRX2VuY3J5cHRpb25fbm9u' + 'Y2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhQg4KDF93aWRnZXRfb25seRpOCgVTdG9yeR' + 'ItCgVtZWRpYRgBIAEoCzIXLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFSBW1lZGlhEhYKBm5vdGlm' + 'eRgCIAEoCFIGbm90aWZ5GqkBCgtNZWRpYVVwZGF0ZRI2CgR0eXBlGAEgASgOMiIuRW5jcnlwdG' + 'VkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEioKEXRhcmdldF9tZXNzYWdlX2lkGAIg' + 'ASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEh' + 'QKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUu' + 'RW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUk' + 'VRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGtkDCg1Db250YWN0VXBkYXRlEjgKBHR5' + 'cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRI3Ch' + 'VhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIf' + 'Cgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgBARImCgxkaXNwbGF5X25hbWUYBCABKAlIAl' + 'ILZGlzcGxheU5hbWWIAQESOwoXY3VzdG9tX2F2YXRhcl9yZXF1ZXN0ZWQYBSABKAhIA1IVY3Vz' + 'dG9tQXZhdGFyUmVxdWVzdGVkiAEBEkgKDWN1c3RvbV9hdmF0YXIYBiABKAsyHi5FbmNyeXB0ZW' + 'RDb250ZW50LkN1c3RvbUF2YXRhckgEUgxjdXN0b21BdmF0YXKIAQEiHwoEVHlwZRILCgdSRVFV' + 'RVNUEAASCgoGVVBEQVRFEAFCGAoWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5hbW' + 'VCDwoNX2Rpc3BsYXlfbmFtZUIaChhfY3VzdG9tX2F2YXRhcl9yZXF1ZXN0ZWRCEAoOX2N1c3Rv' + 'bV9hdmF0YXIazgIKDEN1c3RvbUF2YXRhchIYCgd2ZXJzaW9uGAEgASgNUgd2ZXJzaW9uEi8KE3' + 'B1YmxpY2F0aW9uX2NvdW50ZXIYAiABKANSEnB1YmxpY2F0aW9uQ291bnRlchI6CgVzdGF0ZRgD' + 'IAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ3VzdG9tQXZhdGFyLlN0YXRlUgVzdGF0ZRIXCgR3ZW' + 'JwGAQgASgMSABSBHdlYnCIAQESGwoGc2hhMjU2GAUgASgMSAFSBnNoYTI1NogBARIZCgV3aWR0' + 'aBgGIAEoDUgCUgV3aWR0aIgBARIbCgZoZWlnaHQYByABKA1IA1IGaGVpZ2h0iAEBIiAKBVN0YX' + 'RlEgwKCFNWR19PTkxZEAASCQoFUEhPVE8QAUIHCgVfd2VicEIJCgdfc2hhMjU2QggKBl93aWR0' + 'aEIJCgdfaGVpZ2h0Gq8BCglGbGFtZVN5bmMSIwoNZmxhbWVfY291bnRlchgBIAEoA1IMZmxhbW' + 'VDb3VudGVyEjkKGWxhc3RfZmxhbWVfY291bnRlcl9jaGFuZ2UYAiABKANSFmxhc3RGbGFtZUNv' + 'dW50ZXJDaGFuZ2USHwoLYmVzdF9mcmllbmQYAyABKAhSCmJlc3RGcmllbmQSIQoMZm9yY2VfdX' + 'BkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZRpNCg9UeXBpbmdJbmRpY2F0b3ISGwoJaXNfdHlwaW5n' + 'GAEgASgIUghpc1R5cGluZxIdCgpjcmVhdGVkX2F0GAIgASgDUgljcmVhdGVkQXQaPwoUVXNlck' + 'Rpc2NvdmVyeVJlcXVlc3QSJwoPY3VycmVudF92ZXJzaW9uGAEgASgMUg5jdXJyZW50VmVyc2lv' + 'bhoxChNVc2VyRGlzY292ZXJ5VXBkYXRlEhoKCG1lc3NhZ2VzGAEgAygMUghtZXNzYWdlcxo9Ch' + 'RLZXlWZXJpZmljYXRpb25Qcm9vZhIlCg5jYWxjdWxhdGVkX21hYxgBIAEoDFINY2FsY3VsYXRl' + 'ZE1hYxqbAQoUUGFzc3dvcmRMZXNzUmVjb3ZlcnkSNQoTcmVjb3ZlcnlTZWNyZXRTaGFyZRgBIA' + 'EoDEgAUhNyZWNvdmVyeVNlY3JldFNoYXJliAEBEhYKBmRlbGV0ZRgCIAEoCFIGZGVsZXRlEhwK' + 'CXRocmVzaG9sZBgDIAEoA1IJdGhyZXNob2xkQhYKFF9yZWNvdmVyeVNlY3JldFNoYXJlGjMKHV' + 'Bhc3N3b3JkTGVzc1JlY292ZXJ5SGVhcnRiZWF0EhIKBGhhc2gYASABKAxSBGhhc2hCCwoJX2dy' + 'b3VwX2lkQhEKD19pc19kaXJlY3RfY2hhdEIZChdfc2VuZGVyX3Byb2ZpbGVfY291bnRlckIgCh' + '5fc2VuZGVyX3VzZXJfZGlzY292ZXJ5X3ZlcnNpb25CHAoaX2Fza19mb3JfZnJpZW5kX3Byb21v' + 'dGlvbnNCGQoXX3dpZGdldF9zaGFyaW5nX2FsbG93ZWRCFgoUX3NlbmRlcl90d29ubHlfc2Nvcm' + 'VCHwodX3NlbmRlcl9jdXN0b21fYXZhdGFyX2NvdW50ZXJCIQofX2N1c3RvbV9hdmF0YXJfcHJv' + 'dG9jb2xfdmVyc2lvbkIRCg9fbWVzc2FnZV91cGRhdGVCCAoGX21lZGlhQg8KDV9tZWRpYV91cG' + 'RhdGVCEQoPX2NvbnRhY3RfdXBkYXRlQhIKEF9jb250YWN0X3JlcXVlc3RCDQoLX2ZsYW1lX3N5' + 'bmNCCwoJX3JlYWN0aW9uQg8KDV90ZXh0X21lc3NhZ2VCDwoNX2dyb3VwX2NyZWF0ZUINCgtfZ3' + 'JvdXBfam9pbkIPCg1fZ3JvdXBfdXBkYXRlQhoKGF9yZXNlbmRfZ3JvdXBfcHVibGljX2tleUIR' + 'Cg9fZXJyb3JfbWVzc2FnZXNCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlQhMKEV90eXBpbm' + 'dfaW5kaWNhdG9yQhkKF191c2VyX2Rpc2NvdmVyeV9yZXF1ZXN0QhgKFl91c2VyX2Rpc2NvdmVy' + 'eV91cGRhdGVCGQoXX2tleV92ZXJpZmljYXRpb25fcHJvb2ZCGAoWX3Bhc3N3b3JkbGVzc19yZW' + 'NvdmVyeUIiCiBfcGFzc3dvcmRsZXNzX3JlY292ZXJ5X2hlYXJ0YmVhdEIICgZfc3RvcnlKBAgc' + 'EB0='); diff --git a/lib/src/visual/components/avatar_icon.comp.dart b/lib/src/visual/components/avatar_icon.comp.dart index ca810948..063786a7 100644 --- a/lib/src/visual/components/avatar_icon.comp.dart +++ b/lib/src/visual/components/avatar_icon.comp.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/foundation.dart' show listEquals, setEquals; +import 'package:flutter/foundation.dart' show listEquals; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:twonly/locator.dart'; @@ -80,6 +80,7 @@ String _avatarPngPathFor(Contact contact) { class _AvatarIconState extends State { List _avatarContacts = []; Set _contactsWithPngAvatar = {}; + final Map _resolvedAvatarPaths = {}; String? _myAvatarPath; StreamSubscription>? groupStream; @@ -105,35 +106,20 @@ class _AvatarIconState extends State { } void _setAvatarContacts(List contacts) { - _avatarContacts = contacts - .where((contact) => contact.avatarSvgCompressed != null) - .toList(); + _avatarContacts = contacts; unawaited(_refreshAvatarFiles()); if (mounted) setState(() {}); } Future _refreshAvatarFiles() async { - final available = {}; for (final contact in _avatarContacts) { - final path = _avatarPngPathFor(contact); - var exists = _existingAvatarPngPaths.contains(path); - if (!exists) { - // Async file access keeps avatar discovery off the UI thread. - // ignore: avoid_slow_async_io - exists = await File(path).exists(); - if (exists) _existingAvatarPngPaths.add(path); - } - if (exists) { - available.add(contact.userId); - } else if (!_unrenderableAvatars.contains(_avatarCacheKey(contact))) { - // No PNG yet: render one rather than falling back to the SVG. The - // default avatar is shown until it lands. + if (!_unrenderableAvatars.contains(_avatarCacheKey(contact))) { + // Rust resolves a received custom avatar before the SVG fallback. Do + // not trust the old profile-counter cache here: a custom-avatar update + // deliberately leaves that public counter unchanged. unawaited(_renderAvatarPng(contact)); } } - if (!mounted) return; - if (setEquals(_contactsWithPngAvatar, available)) return; - setState(() => _contactsWithPngAvatar = available); } Future _renderAvatarPng(Contact contact) async { @@ -167,6 +153,7 @@ class _AvatarIconState extends State { if (_contactsWithPngAvatar.contains(contactId)) return; if (!_avatarContacts.any((entry) => entry.userId == contactId)) return; setState(() { + _resolvedAvatarPaths[contactId] = path!; _contactsWithPngAvatar = {..._contactsWithPngAvatar, contactId}; }); } @@ -189,7 +176,9 @@ class _AvatarIconState extends State { Widget getAvatarForContact(Contact contact) { if (_contactsWithPngAvatar.contains(contact.userId)) { return Image.file( - File(_avatarPngPathFor(contact)), + File( + _resolvedAvatarPaths[contact.userId] ?? _avatarPngPathFor(contact), + ), errorBuilder: errorBuilder, ); } @@ -206,16 +195,13 @@ class _AvatarIconState extends State { .watchGroupContact(widget.group!.groupId) .listen((contacts) { _avatarContacts = []; + for (final contact in contacts) { + _unrenderableAvatars.remove(_avatarCacheKey(contact)); + } if (contacts.length == 1) { - if (contacts.first.avatarSvgCompressed != null) { - _avatarContacts.add(contacts.first); - } + _avatarContacts.add(contacts.first); } else { - for (final contact in contacts) { - if (contact.avatarSvgCompressed != null) { - _avatarContacts.add(contact); - } - } + _avatarContacts.addAll(contacts); } unawaited(_refreshAvatarFiles()); setState(() {}); @@ -229,7 +215,8 @@ class _AvatarIconState extends State { contactStream = twonlyDB.contactsDao .watchContact(widget.contactId!) .listen((contact) { - if (contact != null && contact.avatarSvgCompressed != null) { + if (contact != null) { + _unrenderableAvatars.remove(_avatarCacheKey(contact)); _avatarContacts = [contact]; unawaited(_refreshAvatarFiles()); setState(() {}); diff --git a/lib/src/visual/views/camera/share_image_contact_selection_components/story_target_selector.comp.dart b/lib/src/visual/views/camera/share_image_contact_selection_components/story_target_selector.comp.dart index a34abce4..11de1b44 100644 --- a/lib/src/visual/views/camera/share_image_contact_selection_components/story_target_selector.comp.dart +++ b/lib/src/visual/views/camera/share_image_contact_selection_components/story_target_selector.comp.dart @@ -82,7 +82,10 @@ class _StoryTargetSelectorState extends State { ); if (before != widget.target.contactGroupIds.length) _changed(); }); - unawaited(_updateAudience()); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + unawaited(_updateAudience()); + }); } @override diff --git a/lib/src/visual/views/camera/share_image_editor.view.dart b/lib/src/visual/views/camera/share_image_editor.view.dart index 9bbadaff..96b1aba5 100644 --- a/lib/src/visual/views/camera/share_image_editor.view.dart +++ b/lib/src/visual/views/camera/share_image_editor.view.dart @@ -5,11 +5,13 @@ import 'dart:math' as math; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/keyvalue.keys.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/user.service.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart'; import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; @@ -96,6 +98,7 @@ class _ShareImageEditorView extends State { bool _widgetRecipientAvailable = false; bool _sendToWidget = false; bool _updatingWidgetMode = false; + bool _widgetExplainerPreferenceLoaded = false; bool _widgetExplainerDismissed = userService.currentUser.hideWidgetShareExplainer; bool _previousMediaSettingsCaptured = false; @@ -103,7 +106,9 @@ class _ShareImageEditorView extends State { bool _requiresAuthBeforeWidget = false; bool get _showWidgetExplainer => - _widgetRecipientAvailable && !_widgetExplainerDismissed; + _widgetRecipientAvailable && + _widgetExplainerPreferenceLoaded && + !_widgetExplainerDismissed; MediaFileService get mediaService => widget.mediaFileService; MediaFile get media => widget.mediaFileService.mediaFile; @@ -138,6 +143,7 @@ class _ShareImageEditorView extends State { _widgetGroupsSubscription = twonlyDB.groupsDao .watchGroupsAllowedForWidgetShare() .listen(_updateWidgetRecipientAvailability); + unawaited(_loadWidgetExplainerPreference()); if (media.type == MediaType.image || media.type == MediaType.gif) { _loadInitialImage(); } @@ -162,9 +168,27 @@ class _ShareImageEditorView extends State { super.dispose(); } + Future _loadWidgetExplainerPreference() async { + final preference = await KeyValueStore.get( + KeyValueKeys.shareImageWidgetExplainer, + ); + if (!mounted) return; + setState(() { + _widgetExplainerDismissed = + _widgetExplainerDismissed || preference?['dismissed'] == true; + _widgetExplainerPreferenceLoaded = true; + }); + } + Future _dismissWidgetExplainer() async { setState(() => _widgetExplainerDismissed = true); - await UserService.update((u) => u.hideWidgetShareExplainer = true); + await Future.wait([ + KeyValueStore.put( + KeyValueKeys.shareImageWidgetExplainer, + const {'dismissed': true}, + ), + UserService.update((u) => u.hideWidgetShareExplainer = true), + ]); } void _updateWidgetRecipientAvailability(List widgetGroups) { diff --git a/lib/src/visual/views/memories/sticker_source_picker.view.dart b/lib/src/visual/views/memories/sticker_source_picker.view.dart index 35d9a642..0c6e6717 100644 --- a/lib/src/visual/views/memories/sticker_source_picker.view.dart +++ b/lib/src/visual/views/memories/sticker_source_picker.view.dart @@ -11,7 +11,9 @@ import 'package:twonly/src/visual/views/memories/components/memory_thumbnail.com /// Selects a full-resolution image from twonly Memories, with an explicit /// escape hatch to the device gallery in the top app bar. class StickerSourcePickerView extends StatefulWidget { - const StickerSourcePickerView({super.key}); + const StickerSourcePickerView({this.forAvatar = false, super.key}); + + final bool forAvatar; @override State createState() => @@ -63,7 +65,11 @@ class _StickerSourcePickerViewState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.lang.createSticker), + title: Text( + widget.forAvatar + ? context.lang.customAvatarChooseSource + : context.lang.createSticker, + ), actions: [ TextButton.icon( onPressed: _resolvingMediaId == null ? _pickFromGallery : null, diff --git a/lib/src/visual/views/settings/privacy/public_profile.view.dart b/lib/src/visual/views/settings/privacy/public_profile.view.dart index e5ba9f7a..c76c31a2 100644 --- a/lib/src/visual/views/settings/privacy/public_profile.view.dart +++ b/lib/src/visual/views/settings/privacy/public_profile.view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:twonly/core/services/avatars.dart'; import 'package:twonly/core/user_config.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/services/user.service.dart'; @@ -13,6 +14,38 @@ class PublicProfilePrivacyView extends StatefulWidget { } class _PublicProfilePrivacyViewState extends State { + CustomAvatarInfo? _customAvatar; + bool _updatingAvatarVisibility = false; + + @override + void initState() { + super.initState(); + _loadCustomAvatarVisibility(); + } + + Future _loadCustomAvatarVisibility() async { + try { + final avatar = await RustApi.getCustomAvatar(); + if (mounted) setState(() => _customAvatar = avatar); + } on Object { + // Keep the setting disabled when storage is unavailable. Other public + // profile settings remain usable. + } + } + + Future _setCustomAvatarVisibility(bool acceptedOnly) async { + if (_updatingAvatarVisibility) return; + setState(() => _updatingAvatarVisibility = true); + try { + final avatar = await RustApi.setCustomAvatarAudience( + acceptedContactsOnly: acceptedOnly, + ); + if (mounted) setState(() => _customAvatar = avatar); + } finally { + if (mounted) setState(() => _updatingAvatarVisibility = false); + } + } + Future _setTwonlyScoreVisibility( TwonlyScoreVisibility visibility, ) async { @@ -69,6 +102,36 @@ class _PublicProfilePrivacyViewState extends State { ), ), ), + ListTile( + title: Text(context.lang.customAvatarPhotoTab), + subtitle: Text(context.lang.customAvatarExplanation), + trailing: SizedBox( + width: 180, + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: _customAvatar?.acceptedContactsOnly ?? true, + items: [ + DropdownMenuItem( + value: true, + child: Text(context.lang.customAvatarAcceptedOnly), + ), + DropdownMenuItem( + value: false, + child: Text(context.lang.privacyVisibilityEveryone), + ), + ], + onChanged: _customAvatar == null || _updatingAvatarVisibility + ? null + : (acceptedOnly) { + if (acceptedOnly != null) { + _setCustomAvatarVisibility(acceptedOnly); + } + }, + ), + ), + ), + ), ], ), ); diff --git a/lib/src/visual/views/settings/profile/modify_avatar.view.dart b/lib/src/visual/views/settings/profile/modify_avatar.view.dart index de4c28e9..b6ed66be 100644 --- a/lib/src/visual/views/settings/profile/modify_avatar.view.dart +++ b/lib/src/visual/views/settings/profile/modify_avatar.view.dart @@ -2,11 +2,15 @@ import 'dart:math'; import 'package:avatar_maker/avatar_maker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/core/services/avatars.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/elements/my_button.element.dart'; +import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; +import 'package:twonly/src/visual/views/memories/sticker_source_picker.view.dart'; class ModifyAvatarView extends StatefulWidget { const ModifyAvatarView({super.key}); @@ -16,30 +20,340 @@ class ModifyAvatarView extends StatefulWidget { } class _ModifyAvatarViewState extends State { + CustomAvatarInfo? _customAvatar; + bool _loadingCustomAvatar = true; + bool _processingCustomAvatar = false; + bool _updatingAudience = false; + + @override + void initState() { + super.initState(); + _loadCustomAvatar(); + } + + Future _loadCustomAvatar() async { + try { + final avatar = await RustApi.getCustomAvatar(); + if (mounted) setState(() => _customAvatar = avatar); + } finally { + if (mounted) setState(() => _loadingCustomAvatar = false); + } + } + + Future _chooseCustomAvatar() async { + if (_processingCustomAvatar) return; + final path = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const StickerSourcePickerView(forAvatar: true), + ), + ); + if (!mounted || path == null) return; + setState(() => _processingCustomAvatar = true); + try { + final avatar = await RustApi.createCustomAvatar( + imagePath: path, + acceptedContactsOnly: _customAvatar?.acceptedContactsOnly ?? true, + ); + if (!mounted) return; + setState(() => _customAvatar = avatar); + userService.triggerUserUpdate(); + } on Object { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.lang.customAvatarCreateFailed)), + ); + } finally { + if (mounted) setState(() => _processingCustomAvatar = false); + } + } + + Future _removeCustomAvatar() async { + if (_processingCustomAvatar) return; + setState(() => _processingCustomAvatar = true); + try { + final avatar = await RustApi.removeCustomAvatar(); + if (!mounted) return; + setState(() => _customAvatar = avatar); + userService.triggerUserUpdate(); + } finally { + if (mounted) setState(() => _processingCustomAvatar = false); + } + } + + Future _setCustomAvatarAudience(bool acceptedContactsOnly) async { + if (_updatingAudience) return; + setState(() => _updatingAudience = true); + try { + final avatar = await RustApi.setCustomAvatarAudience( + acceptedContactsOnly: acceptedContactsOnly, + ); + if (mounted) setState(() => _customAvatar = avatar); + } on Object { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.lang.errorInternalError)), + ); + } finally { + if (mounted) setState(() => _updatingAudience = false); + } + } + + Future _openAvatarMaker() async { + await Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const _SvgAvatarEditorView()), + ); + if (mounted) setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.lang.settingsProfileCustomizeAvatar), + ), + body: Stack( + children: [ + ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildAvatarMakerCard(context), + const SizedBox(height: 16), + _buildPhotoAvatarCard(context), + const SizedBox(height: 30), + ], + ), + if (_processingCustomAvatar) + const Positioned.fill( + child: ColoredBox( + color: Color(0x66000000), + child: Center(child: ThreeRotatingDots(size: 36)), + ), + ), + ], + ), + ); + } + + Widget _buildAvatarMakerCard(BuildContext context) { + final svg = userService.currentUser.avatarSvg; + return Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + context.lang.customAvatarSvgTab, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 16), + Center( + child: ClipOval( + child: SizedBox.square( + dimension: 150, + child: ColoredBox( + color: context.color.surfaceContainerHigh, + child: svg != null && svg.isNotEmpty + ? SvgPicture.string(svg) + : const Icon(Icons.person_outline, size: 72), + ), + ), + ), + ), + const SizedBox(height: 16), + Center( + child: MyButton( + variant: MyButtonVariant.secondaryMiddle, + onPressed: _openAvatarMaker, + child: Text(context.lang.customAvatarCustomize), + ), + ), + ], + ), + ), + ); + } + + Widget _buildPhotoAvatarCard(BuildContext context) { + if (_loadingCustomAvatar) { + return const Card( + margin: EdgeInsets.zero, + child: SizedBox( + height: 220, + child: Center(child: ThreeRotatingDots(size: 32)), + ), + ); + } + final avatar = _customAvatar; + final hasPhoto = avatar?.webp != null; + return Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + context.lang.customAvatarPhotoTab, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 8), + Text( + context.lang.customAvatarExplanation, + textAlign: TextAlign.center, + style: TextStyle(color: context.color.onSurfaceVariant), + ), + const SizedBox(height: 16), + Center( + child: ClipOval( + child: SizedBox.square( + dimension: 220, + child: ColoredBox( + color: context.color.surfaceContainerHigh, + child: hasPhoto + ? Image.memory(avatar!.webp!, fit: BoxFit.contain) + : const Icon(Icons.person_outline, size: 100), + ), + ), + ), + ), + const SizedBox(height: 20), + Text( + context.lang.customAvatarAudience, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + Center( + child: DropdownButton( + value: avatar?.acceptedContactsOnly ?? true, + alignment: AlignmentDirectional.center, + items: [ + DropdownMenuItem( + value: true, + child: Center( + child: Text( + context.lang.customAvatarAcceptedOnly, + textAlign: TextAlign.center, + ), + ), + ), + DropdownMenuItem( + value: false, + child: Center( + child: Text( + context.lang.customAvatarEveryone, + textAlign: TextAlign.center, + ), + ), + ), + ], + onChanged: _updatingAudience + ? null + : (acceptedContactsOnly) { + if (acceptedContactsOnly != null) { + _setCustomAvatarAudience(acceptedContactsOnly); + } + }, + ), + ), + const SizedBox(height: 20), + Wrap( + alignment: WrapAlignment.center, + spacing: 12, + runSpacing: 12, + children: [ + MyButton( + variant: MyButtonVariant.secondaryMiddle, + onPressed: _processingCustomAvatar + ? null + : _chooseCustomAvatar, + child: Text( + hasPhoto + ? context.lang.customAvatarReplace + : context.lang.customAvatarCreate, + ), + ), + if (hasPhoto) + MyButton( + variant: MyButtonVariant.errorMiddle, + onPressed: _processingCustomAvatar + ? null + : _removeCustomAvatar, + child: Text(context.lang.customAvatarRemove), + ), + ], + ), + ], + ), + ), + ); + } +} + +class _SvgAvatarEditorView extends StatefulWidget { + const _SvgAvatarEditorView(); + + @override + State<_SvgAvatarEditorView> createState() => _SvgAvatarEditorViewState(); +} + +class _SvgAvatarEditorViewState extends State<_SvgAvatarEditorView> { late final _CustomAvatarMakerController _avatarMakerController; @override void initState() { super.initState(); final svg = userService.currentUser.avatarSvg; - if (svg != null && svg.isNotEmpty) { - _avatarMakerController = _CustomAvatarMakerController( - svg: svg, - ); - } else { - _avatarMakerController = _CustomAvatarMakerController.defaultAvatar(); - } + _avatarMakerController = svg != null && svg.isNotEmpty + ? _CustomAvatarMakerController(svg: svg) + : _CustomAvatarMakerController.defaultAvatar(); } - Future updateUserAvatar(String svg) async { + @override + void dispose() { + _avatarMakerController.dispose(); + super.dispose(); + } + + Future _updateUserAvatar(String svg) async { await UserService.update( - (u) => u + (user) => user ..avatarSvg = svg - ..avatarCounter = u.avatarCounter + 1, + ..avatarCounter = user.avatarCounter + 1, ); } - AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) { + Future _storeAvatarAndExit() async { + await _avatarMakerController.saveAvatarSVG(); + final svg = _avatarMakerController.getAvatarSVGSync(); + await _updateUserAvatar(svg); + if (mounted) Navigator.pop(context, true); + } + + Future _showBackDialog() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.lang.avatarSaveChanges), + actions: [ + FilledButton( + onPressed: _storeAvatarAndExit, + child: Text(context.lang.avatarSaveChangesStore), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(context.lang.avatarSaveChangesDiscard), + ), + ], + ), + ); + } + + AvatarMakerThemeData _theme(BuildContext context) { final colors = context.color; return AvatarMakerThemeData( boxDecoration: BoxDecoration( @@ -73,42 +387,6 @@ class _ModifyAvatarViewState extends State { ); } - Future _showBackDialog() { - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text( - context.lang.avatarSaveChanges, - ), - actions: [ - FilledButton( - child: Text(context.lang.avatarSaveChangesStore), - onPressed: () async { - await storeAvatarAndExit(); - }, - ), - TextButton( - child: Text(context.lang.avatarSaveChangesDiscard), - onPressed: () { - Navigator.pop(context, true); - }, - ), - ], - ); - }, - ); - } - - Future storeAvatarAndExit() async { - await _avatarMakerController.saveAvatarSVG(); - final svg = _avatarMakerController.getAvatarSVGSync(); - await updateUserAvatar(svg); - if (mounted) { - Navigator.pop(context, true); - } - } - @override Widget build(BuildContext context) { return PopScope( @@ -117,30 +395,22 @@ class _ModifyAvatarViewState extends State { if (didPop) return; if (_avatarMakerController.getAvatarSVGSync() != userService.currentUser.avatarSvg) { - // there where changes final shouldPop = await _showBackDialog() ?? false; - if (context.mounted && shouldPop) { - Navigator.pop(context); - } + if (context.mounted && shouldPop) Navigator.pop(context); } else { Navigator.pop(context); } }, child: Scaffold( - appBar: AppBar( - title: Text(context.lang.settingsProfileCustomizeAvatar), - ), + appBar: AppBar(title: Text(context.lang.customAvatarSvgTab)), body: Center( child: SingleChildScrollView( child: Column( children: [ - Padding( - padding: EdgeInsets.zero, - child: AvatarMakerAvatar( - radius: 130, - backgroundColor: Colors.transparent, - controller: _avatarMakerController, - ), + AvatarMakerAvatar( + radius: 130, + backgroundColor: Colors.transparent, + controller: _avatarMakerController, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), @@ -151,11 +421,14 @@ class _ModifyAvatarViewState extends State { children: [ MyButton( variant: MyButtonVariant.primaryDense, - onPressed: storeAvatarAndExit, + onPressed: _storeAvatarAndExit, child: Row( mainAxisSize: MainAxisSize.min, children: [ - const FaIcon(FontAwesomeIcons.floppyDisk, size: 16), + const FaIcon( + FontAwesomeIcons.floppyDisk, + size: 16, + ), const SizedBox(width: 6), Text(context.lang.avatarSaveChangesStore), ], @@ -177,13 +450,14 @@ class _ModifyAvatarViewState extends State { MyButton( variant: MyButtonVariant.secondaryDense, onPressed: _avatarMakerController.restoreState, - onLongPress: () { - _avatarMakerController.clearCustomizations(); - }, + onLongPress: _avatarMakerController.clearCustomizations, child: Row( mainAxisSize: MainAxisSize.min, children: [ - const FaIcon(FontAwesomeIcons.rotateLeft, size: 16), + const FaIcon( + FontAwesomeIcons.rotateLeft, + size: 16, + ), const SizedBox(width: 6), Text(context.lang.avatarCustomizeReset), ], @@ -193,15 +467,13 @@ class _ModifyAvatarViewState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric( - vertical: 30, - ), + padding: const EdgeInsets.symmetric(vertical: 30), child: AvatarMakerCustomizer( scaffoldWidth: min( 600, MediaQuery.of(context).size.width * 0.95, ), - theme: getAvatarMakerTheme(context), + theme: _theme(context), controller: _avatarMakerController, ), ), diff --git a/rust/src/api/messages/incoming.rs b/rust/src/api/messages/incoming.rs index ee6c918c..7738d6cd 100644 --- a/rust/src/api/messages/incoming.rs +++ b/rust/src/api/messages/incoming.rs @@ -776,6 +776,7 @@ async fn handle_encrypted_inner( } contact::check_for_profile_update(t, from_user_id, &content).await?; + contact::check_for_custom_avatar_update(t, from_user_id, &content).await?; if content.ask_for_friend_promotions == Some(true) { Contact::update_ask_for_friend_promotions(t, from_user_id).await?; diff --git a/rust/src/api/messages/incoming/contact.rs b/rust/src/api/messages/incoming/contact.rs index 11602a7a..0009ee52 100644 --- a/rust/src/api/messages/incoming/contact.rs +++ b/rust/src/api/messages/incoming/contact.rs @@ -21,6 +21,72 @@ use std::io::Write as _; use std::sync::{Arc, Mutex, OnceLock}; static REQUESTED_PROFILES: OnceLock>> = OnceLock::new(); +static REQUESTED_CUSTOM_AVATARS: OnceLock>> = + OnceLock::new(); + +pub(crate) async fn check_for_custom_avatar_update( + t: &mut Transaction<'_, Sqlite>, + from_user_id: i64, + content: &EncryptedContent, +) -> Result<()> { + if content.custom_avatar_protocol_version.unwrap_or_default() + < crate::services::avatars::CUSTOM_AVATAR_PROTOCOL_VERSION + || content + .contact_update + .as_ref() + .is_some_and(|update| update.custom_avatar.is_some()) + { + return Ok(()); + } + let Some(counter) = content.sender_custom_avatar_counter else { + return Ok(()); + }; + if counter <= crate::services::avatars::applied_counter(t, from_user_id).await? { + return Ok(()); + } + let should_request = { + let mut requested = REQUESTED_CUSTOM_AVATARS + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .unwrap(); + requested.retain(|_, (_, requested_at)| { + requested_at.elapsed() < std::time::Duration::from_secs(120) + }); + let previous = requested + .get(&from_user_id) + .map(|(counter, _)| *counter) + .unwrap_or(-1); + if counter > previous { + requested.insert(from_user_id, (counter, std::time::Instant::now())); + true + } else { + false + } + }; + if should_request { + queue_encrypted_content( + t, + from_user_id, + EncryptedContent { + contact_update: Some(encrypted_content::ContactUpdate { + r#type: encrypted_content::contact_update::Type::Request as i32, + username: None, + display_name: None, + avatar_svg_compressed: None, + custom_avatar_requested: Some(true), + custom_avatar: None, + }), + custom_avatar_protocol_version: Some( + crate::services::avatars::CUSTOM_AVATAR_PROTOCOL_VERSION, + ), + ..Default::default() + }, + true, + ) + .await?; + } + Ok(()) +} pub(crate) async fn check_for_profile_update( t: &mut Transaction<'_, Sqlite>, @@ -68,6 +134,8 @@ pub(crate) async fn check_for_profile_update( username: None, display_name: None, avatar_svg_compressed: None, + custom_avatar_requested: None, + custom_avatar: None, }), ..Default::default() }, @@ -216,6 +284,11 @@ pub(crate) async fn handle_contact_update( }) .transpose()?; + let custom_avatar = if update.custom_avatar_requested == Some(true) { + Some(crate::services::avatars::publication_payload(tr, from_user_id).await?) + } else { + None + }; queue_encrypted_content( tr, from_user_id, @@ -225,8 +298,16 @@ pub(crate) async fn handle_contact_update( username: Some(user.username), display_name: Some(user.display_name), avatar_svg_compressed, + custom_avatar_requested: None, + custom_avatar: custom_avatar.clone(), }), sender_profile_counter: Some(user.avatar_counter), + sender_custom_avatar_counter: custom_avatar + .as_ref() + .map(|avatar| avatar.publication_counter), + custom_avatar_protocol_version: Some( + crate::services::avatars::CUSTOM_AVATAR_PROTOCOL_VERSION, + ), ..Default::default() }, true, @@ -240,6 +321,21 @@ pub(crate) async fn handle_contact_update( } let avatar_svg_compressed = update.avatar_svg_compressed.clone(); + if let Some(custom_avatar) = update.custom_avatar.clone() { + match crate::services::avatars::apply_received(tr, from_user_id, custom_avatar).await { + Ok(_) => { + if let Some(requested) = REQUESTED_CUSTOM_AVATARS.get() { + requested.lock().unwrap().remove(&from_user_id); + } + } + Err(error) => { + // A malformed optional avatar must not keep an otherwise valid + // reliable-mailbox profile response retrying forever. The + // advertised counter stays stale and can be requested again. + tracing::warn!(from_user_id, %error, "discarding invalid custom avatar"); + } + } + } let previous = sqlx::query!( "SELECT username, display_name FROM contacts WHERE user_id = ?", diff --git a/rust/src/api/messages/incoming/messages.rs b/rust/src/api/messages/incoming/messages.rs index 73d8fcf9..52182b6a 100644 --- a/rust/src/api/messages/incoming/messages.rs +++ b/rust/src/api/messages/incoming/messages.rs @@ -509,6 +509,9 @@ async fn prepare_queued_receipt_from_row( let plaintext = message.encrypted_content.take().ok_or_else(|| { TwonlyError::Generic("queued encrypted message has no content".into()) })?; + let plaintext = + crate::services::avatars::refresh_queued_response(ctx, row.contact_id, plaintext) + .await?; message.encrypted_content = Some(encrypt_v2_with_session_recovery(ctx, row.contact_id, plaintext).await?); diff --git a/rust/src/api/messages/outgoing.rs b/rust/src/api/messages/outgoing.rs index 96ca5440..2eac6b30 100644 --- a/rust/src/api/messages/outgoing.rs +++ b/rust/src/api/messages/outgoing.rs @@ -56,6 +56,10 @@ pub(crate) async fn decorate_content( }; content.sender_profile_counter = Some(config.avatar_counter); + content.sender_custom_avatar_counter = + Some(crate::services::avatars::prepare_publication(t, contact_id).await?); + content.custom_avatar_protocol_version = + Some(crate::services::avatars::CUSTOM_AVATAR_PROTOCOL_VERSION); content.widget_sharing_allowed = Some( sqlx::query_scalar!( "SELECT widget_sharing_granted FROM contacts WHERE user_id = ?", diff --git a/rust/src/api/proto/client/messages.proto b/rust/src/api/proto/client/messages.proto index 69bd09e6..c105ee47 100644 --- a/rust/src/api/proto/client/messages.proto +++ b/rust/src/api/proto/client/messages.proto @@ -13,6 +13,10 @@ message EncryptedContent { optional bool widget_sharing_allowed = 29; // Sum of all exchanged media, omitted when profile sharing is disabled. optional int64 sender_twonly_score = 31; + // Per-recipient version of the custom photo-avatar state. Receivers request + // the payload only when this advances; the image is never broadcast. + optional int64 sender_custom_avatar_counter = 32; + optional uint32 custom_avatar_protocol_version = 33; optional MessageUpdate message_update = 5; optional Media media = 6; @@ -177,6 +181,25 @@ message EncryptedContent { optional bytes avatar_svg_compressed = 2; optional string username = 3; optional string display_name = 4; + // Set on a REQUEST only by clients that understand CustomAvatar. + optional bool custom_avatar_requested = 5; + // Set on UPDATE only in direct response to such a request. + optional CustomAvatar custom_avatar = 6; + } + + message CustomAvatar { + enum State { + SVG_ONLY = 0; + PHOTO = 1; + } + + uint32 version = 1; + int64 publication_counter = 2; + State state = 3; + optional bytes webp = 4; + optional bytes sha256 = 5; + optional uint32 width = 6; + optional uint32 height = 7; } message FlameSync { diff --git a/rust/src/bridge/api.rs b/rust/src/bridge/api.rs index fc5e7d91..93ab8f7c 100644 --- a/rust/src/bridge/api.rs +++ b/rust/src/bridge/api.rs @@ -198,6 +198,9 @@ impl RustApi { pub async fn current_user_avatar_path() -> Result> { let ctx = Context::get_static()?.clone(); + if let Some(path) = avatars::current_custom_avatar_path(&ctx).await? { + return Ok(Some(path.display().to_string())); + } let path = tokio::task::spawn_blocking(move || avatars::current_user_avatar_path(&ctx)) .await .map_err(|error| { @@ -206,6 +209,33 @@ impl RustApi { Ok(path.map(|path| path.display().to_string())) } + pub async fn get_custom_avatar() -> Result { + avatars::own_custom_avatar(Context::get_static()?).await + } + + pub async fn create_custom_avatar( + image_path: String, + accepted_contacts_only: bool, + ) -> Result { + let avatar = tokio::task::spawn_blocking(move || { + avatars::create_custom_avatar(Path::new(&image_path)) + }) + .await + .map_err(|error| TwonlyError::Generic(format!("avatar creation task failed: {error}")))??; + avatars::store_own_custom_avatar(Context::get_static()?, avatar, accepted_contacts_only) + .await + } + + pub async fn set_custom_avatar_audience( + accepted_contacts_only: bool, + ) -> Result { + avatars::set_custom_avatar_audience(Context::get_static()?, accepted_contacts_only).await + } + + pub async fn remove_custom_avatar() -> Result { + avatars::remove_own_custom_avatar(Context::get_static()?).await + } + /// Settles every background transfer this device believes is still in /// flight and resumes any upload a terminated process left behind. pub async fn finish_started_media_uploads() -> Result<()> { diff --git a/rust/src/database/app/migrations/0031_custom_avatars.sql b/rust/src/database/app/migrations/0031_custom_avatars.sql new file mode 100644 index 00000000..8628a5c2 --- /dev/null +++ b/rust/src/database/app/migrations/0031_custom_avatars.sql @@ -0,0 +1,31 @@ +CREATE TABLE own_custom_avatar ( + singleton INTEGER NOT NULL PRIMARY KEY CHECK(singleton = 1), + webp BLOB CHECK(webp IS NULL OR length(webp) BETWEEN 1 AND 131072), + sha256 BLOB CHECK(sha256 IS NULL OR length(sha256) = 32), + width INTEGER CHECK(width IS NULL OR width BETWEEN 1 AND 300), + height INTEGER CHECK(height IS NULL OR height BETWEEN 1 AND 300), + accepted_contacts_only INTEGER NOT NULL DEFAULT 1 CHECK(accepted_contacts_only IN (0, 1)), + source_revision INTEGER NOT NULL DEFAULT 0 CHECK(source_revision >= 0), + CHECK((webp IS NULL AND sha256 IS NULL AND width IS NULL AND height IS NULL) OR + (webp IS NOT NULL AND sha256 IS NOT NULL AND width IS NOT NULL AND height IS NOT NULL)) +); + +INSERT INTO own_custom_avatar(singleton) VALUES (1); + +CREATE TABLE custom_avatar_publications ( + contact_id INTEGER NOT NULL PRIMARY KEY REFERENCES contacts(user_id) ON DELETE CASCADE, + publication_counter INTEGER NOT NULL DEFAULT 0 CHECK(publication_counter >= 0), + visible_source_revision INTEGER, + shows_photo INTEGER NOT NULL DEFAULT 0 CHECK(shows_photo IN (0, 1)) +); + +CREATE TABLE received_custom_avatars ( + contact_id INTEGER NOT NULL PRIMARY KEY REFERENCES contacts(user_id) ON DELETE CASCADE, + applied_counter INTEGER NOT NULL DEFAULT 0 CHECK(applied_counter >= 0), + webp BLOB CHECK(webp IS NULL OR length(webp) BETWEEN 1 AND 131072), + sha256 BLOB CHECK(sha256 IS NULL OR length(sha256) = 32), + width INTEGER CHECK(width IS NULL OR width BETWEEN 1 AND 300), + height INTEGER CHECK(height IS NULL OR height BETWEEN 1 AND 300), + CHECK((webp IS NULL AND sha256 IS NULL AND width IS NULL AND height IS NULL) OR + (webp IS NOT NULL AND sha256 IS NOT NULL AND width IS NOT NULL AND height IS NOT NULL)) +); diff --git a/rust/src/database/app/mod.rs b/rust/src/database/app/mod.rs index 303699ee..cb1d84de 100644 --- a/rust/src/database/app/mod.rs +++ b/rust/src/database/app/mod.rs @@ -16,7 +16,7 @@ mod legacy_import; pub mod tables; pub const APP_DATABASE_FILE: &str = "app_db.sqlite"; -pub const APP_SCHEMA_VERSION: i64 = 15; +pub const APP_SCHEMA_VERSION: i64 = 16; /// User-owned application tables in the current Rust schema. Rust-only outbox /// tables are deliberately absent because they are reconstructed locally. diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 911fa706..0b15ad31 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1901082359; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 926647182; // Section: executor @@ -1880,6 +1880,47 @@ fn wire__crate__bridge__api__rust_api_connection_state_impl( }, ) } +fn wire__crate__bridge__api__rust_api_create_custom_avatar_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_create_custom_avatar", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_image_path = ::sse_decode(&mut deserializer); + let api_accepted_contacts_only = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::create_custom_avatar( + api_image_path, + api_accepted_contacts_only, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__api__rust_api_create_sticker_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -2468,6 +2509,41 @@ fn wire__crate__bridge__api__rust_api_force_ipa_check_impl( }, ) } +fn wire__crate__bridge__api__rust_api_get_custom_avatar_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_get_custom_avatar", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::get_custom_avatar().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__api__rust_api_get_memories_url_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -3798,6 +3874,41 @@ fn wire__crate__bridge__api__rust_api_remove_additional_user_impl( }, ) } +fn wire__crate__bridge__api__rust_api_remove_custom_avatar_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_remove_custom_avatar", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::remove_custom_avatar().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__api__rust_api_remove_media_files_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -4548,6 +4659,45 @@ fn wire__crate__bridge__api__rust_api_set_background_impl( }, ) } +fn wire__crate__bridge__api__rust_api_set_custom_avatar_audience_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "rust_api_set_custom_avatar_audience", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_accepted_contacts_only = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || async move { + let output_ok = crate::bridge::api::RustApi::set_custom_avatar_audience( + api_accepted_contacts_only, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__bridge__api__rust_api_set_home_widget_groups_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -6908,6 +7058,26 @@ impl SseDecode for bool { } } +impl SseDecode for crate::services::avatars::CustomAvatarInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_webp = >>::sse_decode(deserializer); + let mut var_sha256 = >>::sse_decode(deserializer); + let mut var_width = >::sse_decode(deserializer); + let mut var_height = >::sse_decode(deserializer); + let mut var_acceptedContactsOnly = ::sse_decode(deserializer); + let mut var_sourceRevision = ::sse_decode(deserializer); + return crate::services::avatars::CustomAvatarInfo { + webp: var_webp, + sha256: var_sha256, + width: var_width, + height: var_height, + accepted_contacts_only: var_acceptedContactsOnly, + source_revision: var_sourceRevision, + }; + } +} + impl SseDecode for f64 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -8245,134 +8415,138 @@ fn pde_ffi_dispatcher_primary_impl( 49 => wire__crate__bridge__api__rust_api_confirm_memories_upload_impl(port, ptr, rust_vec_len, data_len), 50 => wire__crate__bridge__api__rust_api_connect_impl(port, ptr, rust_vec_len, data_len), 51 => wire__crate__bridge__api__rust_api_connection_state_impl(port, ptr, rust_vec_len, data_len), -52 => wire__crate__bridge__api__rust_api_create_sticker_impl(port, ptr, rust_vec_len, data_len), -53 => wire__crate__bridge__api__rust_api_crop_media_transparent_borders_impl(port, ptr, rust_vec_len, data_len), -54 => wire__crate__bridge__api__rust_api_current_user_avatar_path_impl(port, ptr, rust_vec_len, data_len), -56 => wire__crate__bridge__api__rust_api_delete_account_impl(port, ptr, rust_vec_len, data_len), -57 => wire__crate__bridge__api__rust_api_delete_memory_impl(port, ptr, rust_vec_len, data_len), -58 => wire__crate__bridge__api__rust_api_delete_widget_media_impl(port, ptr, rust_vec_len, data_len), -59 => wire__crate__bridge__api__rust_api_disable_memories_backup_impl(port, ptr, rust_vec_len, data_len), -60 => wire__crate__bridge__api__rust_api_download_done_impl(port, ptr, rust_vec_len, data_len), -61 => wire__crate__bridge__api__rust_api_download_media_impl(port, ptr, rust_vec_len, data_len), -62 => wire__crate__bridge__api__rust_api_download_pending_media_impl(port, ptr, rust_vec_len, data_len), -63 => wire__crate__bridge__api__rust_api_ensure_avatar_png_impl(port, ptr, rust_vec_len, data_len), -64 => wire__crate__bridge__api__rust_api_establish_signal_session_impl(port, ptr, rust_vec_len, data_len), -65 => wire__crate__bridge__api__rust_api_events_impl(port, ptr, rust_vec_len, data_len), -66 => wire__crate__bridge__api__rust_api_finish_started_media_uploads_impl(port, ptr, rust_vec_len, data_len), -67 => wire__crate__bridge__api__rust_api_force_ipa_check_impl(port, ptr, rust_vec_len, data_len), -68 => wire__crate__bridge__api__rust_api_get_memories_url_impl(port, ptr, rust_vec_len, data_len), -69 => wire__crate__bridge__api__rust_api_get_memories_usage_impl(port, ptr, rust_vec_len, data_len), -70 => wire__crate__bridge__api__rust_api_get_plan_balance_impl(port, ptr, rust_vec_len, data_len), -71 => wire__crate__bridge__api__rust_api_get_proof_of_work_impl(port, ptr, rust_vec_len, data_len), -72 => wire__crate__bridge__api__rust_api_get_server_key_for_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len), -73 => wire__crate__bridge__api__rust_api_get_user_by_id_impl(port, ptr, rust_vec_len, data_len), -74 => wire__crate__bridge__api__rust_api_get_user_data_impl(port, ptr, rust_vec_len, data_len), -75 => wire__crate__bridge__api__rust_api_get_user_id_from_username_impl(port, ptr, rust_vec_len, data_len), -76 => wire__crate__bridge__api__rust_api_hand_outbox_to_os_impl(port, ptr, rust_vec_len, data_len), -77 => wire__crate__bridge__api__rust_api_initialize_media_upload_impl(port, ptr, rust_vec_len, data_len), -78 => wire__crate__bridge__api__rust_api_initialize_memory_location_impl(port, ptr, rust_vec_len, data_len), -79 => wire__crate__bridge__api__rust_api_insert_and_send_additional_data_impl(port, ptr, rust_vec_len, data_len), -80 => wire__crate__bridge__api__rust_api_insert_and_send_ask_about_user_impl(port, ptr, rust_vec_len, data_len), -81 => wire__crate__bridge__api__rust_api_insert_and_send_contact_share_impl(port, ptr, rust_vec_len, data_len), -82 => wire__crate__bridge__api__rust_api_insert_and_send_text_impl(port, ptr, rust_vec_len, data_len), -83 => wire__crate__bridge__api__rust_api_ipa_purchase_impl(port, ptr, rust_vec_len, data_len), -84 => wire__crate__bridge__api__rust_api_load_plan_balance_impl(port, ptr, rust_vec_len, data_len), -85 => wire__crate__bridge__api__rust_api_media_size_limit_report_impl(port, ptr, rust_vec_len, data_len), -86 => wire__crate__bridge__api__rust_api_media_step_finished_impl(port, ptr, rust_vec_len, data_len), -87 => wire__crate__bridge__api__rust_api_notification_badge_count_impl(port, ptr, rust_vec_len, data_len), -88 => wire__crate__bridge__api__rust_api_notify_messages_opened_impl(port, ptr, rust_vec_len, data_len), -89 => wire__crate__bridge__api__rust_api_perform_passwordless_recovery_heartbeat_impl(port, ptr, rust_vec_len, data_len), -90 => wire__crate__bridge__api__rust_api_prerender_media_impl(port, ptr, rust_vec_len, data_len), -91 => wire__crate__bridge__api__rust_api_prewarm_memory_location_impl(port, ptr, rust_vec_len, data_len), -92 => wire__crate__bridge__api__rust_api_purge_media_temp_folder_impl(port, ptr, rust_vec_len, data_len), -93 => wire__crate__bridge__api__rust_api_purge_widget_media_impl(port, ptr, rust_vec_len, data_len), -94 => wire__crate__bridge__api__rust_api_refresh_contact_joined_dates_impl(port, ptr, rust_vec_len, data_len), -95 => wire__crate__bridge__api__rust_api_refresh_widget_manifest_impl(port, ptr, rust_vec_len, data_len), -96 => wire__crate__bridge__api__rust_api_register_impl(port, ptr, rust_vec_len, data_len), -97 => wire__crate__bridge__api__rust_api_register_home_widget_impl(port, ptr, rust_vec_len, data_len), -98 => wire__crate__bridge__api__rust_api_register_passwordless_notification_impl(port, ptr, rust_vec_len, data_len), -99 => wire__crate__bridge__api__rust_api_register_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len), -100 => wire__crate__bridge__api__rust_api_reload_configuration_impl(port, ptr, rust_vec_len, data_len), -101 => wire__crate__bridge__api__rust_api_remove_additional_user_impl(port, ptr, rust_vec_len, data_len), -102 => wire__crate__bridge__api__rust_api_remove_media_files_impl(port, ptr, rust_vec_len, data_len), -103 => wire__crate__bridge__api__rust_api_report_user_impl(port, ptr, rust_vec_len, data_len), -104 => wire__crate__bridge__api__rust_api_request_binary_impl(port, ptr, rust_vec_len, data_len), -105 => wire__crate__bridge__api__rust_api_request_contact_by_username_impl(port, ptr, rust_vec_len, data_len), -106 => wire__crate__bridge__api__rust_api_request_media_reupload_impl(port, ptr, rust_vec_len, data_len), -107 => wire__crate__bridge__api__rust_api_request_memories_upload_impl(port, ptr, rust_vec_len, data_len), -108 => wire__crate__bridge__api__rust_api_retransmit_all_messages_impl(port, ptr, rust_vec_len, data_len), -109 => wire__crate__bridge__api__rust_api_retry_pending_media_reuploads_impl(port, ptr, rust_vec_len, data_len), -110 => wire__crate__bridge__api__rust_api_reupload_pending_media_impl(port, ptr, rust_vec_len, data_len), -111 => wire__crate__bridge__api__rust_api_save_media_to_gallery_impl(port, ptr, rust_vec_len, data_len), -112 => wire__crate__bridge__api__rust_api_send_binary_impl(port, ptr, rust_vec_len, data_len), -113 => wire__crate__bridge__api__rust_api_send_contact_profile_impl(port, ptr, rust_vec_len, data_len), -114 => wire__crate__bridge__api__rust_api_send_encrypted_content_impl(port, ptr, rust_vec_len, data_len), -115 => wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl(port, ptr, rust_vec_len, data_len), -116 => wire__crate__bridge__api__rust_api_send_media_to_groups_impl(port, ptr, rust_vec_len, data_len), -117 => wire__crate__bridge__api__rust_api_send_queued_message_impl(port, ptr, rust_vec_len, data_len), -118 => wire__crate__bridge__api__rust_api_send_text_message_impl(port, ptr, rust_vec_len, data_len), -119 => wire__crate__bridge__api__rust_api_send_typing_impl(port, ptr, rust_vec_len, data_len), -120 => wire__crate__bridge__api__rust_api_set_background_impl(port, ptr, rust_vec_len, data_len), -121 => wire__crate__bridge__api__rust_api_set_home_widget_groups_impl(port, ptr, rust_vec_len, data_len), -122 => wire__crate__bridge__api__rust_api_set_login_token_impl(port, ptr, rust_vec_len, data_len), -123 => wire__crate__bridge__api__rust_api_set_media_display_limit_impl(port, ptr, rust_vec_len, data_len), -124 => wire__crate__bridge__api__rust_api_set_media_requires_authentication_impl(port, ptr, rust_vec_len, data_len), -125 => wire__crate__bridge__api__rust_api_set_media_trim_impl(port, ptr, rust_vec_len, data_len), -126 => wire__crate__bridge__api__rust_api_set_network_available_impl(port, ptr, rust_vec_len, data_len), -127 => wire__crate__bridge__api__rust_api_store_media_impl(port, ptr, rust_vec_len, data_len), -128 => wire__crate__bridge__api__rust_api_submit_recovery_share_impl(port, ptr, rust_vec_len, data_len), -129 => wire__crate__bridge__api__rust_api_sync_widget_permissions_impl(port, ptr, rust_vec_len, data_len), -130 => wire__crate__bridge__api__rust_api_toggle_media_remove_audio_impl(port, ptr, rust_vec_len, data_len), -131 => wire__crate__bridge__api__rust_api_try_request_contact_by_id_impl(port, ptr, rust_vec_len, data_len), -132 => wire__crate__bridge__api__rust_api_unregister_home_widget_impl(port, ptr, rust_vec_len, data_len), -133 => wire__crate__bridge__api__rust_api_update_fcm_token_impl(port, ptr, rust_vec_len, data_len), -134 => wire__crate__bridge__api__rust_api_update_signed_pre_key_impl(port, ptr, rust_vec_len, data_len), -135 => wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(port, ptr, rust_vec_len, data_len), -136 => wire__crate__bridge__wrapper__app_database__rust_app_database_changes_impl(port, ptr, rust_vec_len, data_len), -137 => wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl(port, ptr, rust_vec_len, data_len), -138 => wire__crate__bridge__wrapper__app_database__rust_app_database_legacy_import_complete_impl(port, ptr, rust_vec_len, data_len), -139 => wire__crate__bridge__wrapper__app_database__rust_app_database_migrate_legacy_database_impl(port, ptr, rust_vec_len, data_len), -140 => wire__crate__bridge__wrapper__app_database__rust_app_database_select_impl(port, ptr, rust_vec_len, data_len), -141 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len), -142 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len), -143 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len), -144 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len), -145 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), -146 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len), -147 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), -148 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len), -149 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), -150 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), -151 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), -152 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len), -153 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len), -154 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len), -155 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len), -156 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_local_credentials_impl(port, ptr, rust_vec_len, data_len), -157 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len), -158 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len), -159 => wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(port, ptr, rust_vec_len, data_len), -160 => wire__crate__bridge__wrapper__signal__rust_signal_encrypt_impl(port, ptr, rust_vec_len, data_len), -161 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len), -162 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len), -163 => wire__crate__bridge__wrapper__signal__rust_signal_get_contact_public_key_impl(port, ptr, rust_vec_len, data_len), -164 => wire__crate__bridge__wrapper__signal__rust_signal_get_safety_number_impl(port, ptr, rust_vec_len, data_len), -165 => wire__crate__bridge__wrapper__signal__rust_signal_get_user_public_key_impl(port, ptr, rust_vec_len, data_len), -166 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len), -167 => wire__crate__bridge__wrapper__signal__rust_signal_reset_contact_session_impl(port, ptr, rust_vec_len, data_len), -168 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len), -169 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len), -170 => wire__crate__bridge__webxdc__send_update_impl(port, ptr, rust_vec_len, data_len), -171 => wire__crate__bridge__webxdc__serve_impl(port, ptr, rust_vec_len, data_len), -172 => wire__crate__bridge__stories__story_audience_chats_impl(port, ptr, rust_vec_len, data_len), -173 => wire__crate__bridge__groups__update_chat_deletion_time_impl(port, ptr, rust_vec_len, data_len), -174 => wire__crate__bridge__groups__update_group_name_impl(port, ptr, rust_vec_len, data_len), -175 => wire__crate__bridge__webxdc__updates_after_impl(port, ptr, rust_vec_len, data_len), -177 => wire__crate__bridge__user_config__user_config_api_create_impl(port, ptr, rust_vec_len, data_len), -178 => wire__crate__bridge__user_config__user_config_api_import_json_impl(port, ptr, rust_vec_len, data_len), -179 => wire__crate__bridge__user_config__user_config_api_load_impl(port, ptr, rust_vec_len, data_len), -180 => wire__crate__bridge__user_config__user_config_api_save_impl(port, ptr, rust_vec_len, data_len), -181 => wire__crate__bridge__user_config__user_config_api_update_impl(port, ptr, rust_vec_len, data_len), +52 => wire__crate__bridge__api__rust_api_create_custom_avatar_impl(port, ptr, rust_vec_len, data_len), +53 => wire__crate__bridge__api__rust_api_create_sticker_impl(port, ptr, rust_vec_len, data_len), +54 => wire__crate__bridge__api__rust_api_crop_media_transparent_borders_impl(port, ptr, rust_vec_len, data_len), +55 => wire__crate__bridge__api__rust_api_current_user_avatar_path_impl(port, ptr, rust_vec_len, data_len), +57 => wire__crate__bridge__api__rust_api_delete_account_impl(port, ptr, rust_vec_len, data_len), +58 => wire__crate__bridge__api__rust_api_delete_memory_impl(port, ptr, rust_vec_len, data_len), +59 => wire__crate__bridge__api__rust_api_delete_widget_media_impl(port, ptr, rust_vec_len, data_len), +60 => wire__crate__bridge__api__rust_api_disable_memories_backup_impl(port, ptr, rust_vec_len, data_len), +61 => wire__crate__bridge__api__rust_api_download_done_impl(port, ptr, rust_vec_len, data_len), +62 => wire__crate__bridge__api__rust_api_download_media_impl(port, ptr, rust_vec_len, data_len), +63 => wire__crate__bridge__api__rust_api_download_pending_media_impl(port, ptr, rust_vec_len, data_len), +64 => wire__crate__bridge__api__rust_api_ensure_avatar_png_impl(port, ptr, rust_vec_len, data_len), +65 => wire__crate__bridge__api__rust_api_establish_signal_session_impl(port, ptr, rust_vec_len, data_len), +66 => wire__crate__bridge__api__rust_api_events_impl(port, ptr, rust_vec_len, data_len), +67 => wire__crate__bridge__api__rust_api_finish_started_media_uploads_impl(port, ptr, rust_vec_len, data_len), +68 => wire__crate__bridge__api__rust_api_force_ipa_check_impl(port, ptr, rust_vec_len, data_len), +69 => wire__crate__bridge__api__rust_api_get_custom_avatar_impl(port, ptr, rust_vec_len, data_len), +70 => wire__crate__bridge__api__rust_api_get_memories_url_impl(port, ptr, rust_vec_len, data_len), +71 => wire__crate__bridge__api__rust_api_get_memories_usage_impl(port, ptr, rust_vec_len, data_len), +72 => wire__crate__bridge__api__rust_api_get_plan_balance_impl(port, ptr, rust_vec_len, data_len), +73 => wire__crate__bridge__api__rust_api_get_proof_of_work_impl(port, ptr, rust_vec_len, data_len), +74 => wire__crate__bridge__api__rust_api_get_server_key_for_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len), +75 => wire__crate__bridge__api__rust_api_get_user_by_id_impl(port, ptr, rust_vec_len, data_len), +76 => wire__crate__bridge__api__rust_api_get_user_data_impl(port, ptr, rust_vec_len, data_len), +77 => wire__crate__bridge__api__rust_api_get_user_id_from_username_impl(port, ptr, rust_vec_len, data_len), +78 => wire__crate__bridge__api__rust_api_hand_outbox_to_os_impl(port, ptr, rust_vec_len, data_len), +79 => wire__crate__bridge__api__rust_api_initialize_media_upload_impl(port, ptr, rust_vec_len, data_len), +80 => wire__crate__bridge__api__rust_api_initialize_memory_location_impl(port, ptr, rust_vec_len, data_len), +81 => wire__crate__bridge__api__rust_api_insert_and_send_additional_data_impl(port, ptr, rust_vec_len, data_len), +82 => wire__crate__bridge__api__rust_api_insert_and_send_ask_about_user_impl(port, ptr, rust_vec_len, data_len), +83 => wire__crate__bridge__api__rust_api_insert_and_send_contact_share_impl(port, ptr, rust_vec_len, data_len), +84 => wire__crate__bridge__api__rust_api_insert_and_send_text_impl(port, ptr, rust_vec_len, data_len), +85 => wire__crate__bridge__api__rust_api_ipa_purchase_impl(port, ptr, rust_vec_len, data_len), +86 => wire__crate__bridge__api__rust_api_load_plan_balance_impl(port, ptr, rust_vec_len, data_len), +87 => wire__crate__bridge__api__rust_api_media_size_limit_report_impl(port, ptr, rust_vec_len, data_len), +88 => wire__crate__bridge__api__rust_api_media_step_finished_impl(port, ptr, rust_vec_len, data_len), +89 => wire__crate__bridge__api__rust_api_notification_badge_count_impl(port, ptr, rust_vec_len, data_len), +90 => wire__crate__bridge__api__rust_api_notify_messages_opened_impl(port, ptr, rust_vec_len, data_len), +91 => wire__crate__bridge__api__rust_api_perform_passwordless_recovery_heartbeat_impl(port, ptr, rust_vec_len, data_len), +92 => wire__crate__bridge__api__rust_api_prerender_media_impl(port, ptr, rust_vec_len, data_len), +93 => wire__crate__bridge__api__rust_api_prewarm_memory_location_impl(port, ptr, rust_vec_len, data_len), +94 => wire__crate__bridge__api__rust_api_purge_media_temp_folder_impl(port, ptr, rust_vec_len, data_len), +95 => wire__crate__bridge__api__rust_api_purge_widget_media_impl(port, ptr, rust_vec_len, data_len), +96 => wire__crate__bridge__api__rust_api_refresh_contact_joined_dates_impl(port, ptr, rust_vec_len, data_len), +97 => wire__crate__bridge__api__rust_api_refresh_widget_manifest_impl(port, ptr, rust_vec_len, data_len), +98 => wire__crate__bridge__api__rust_api_register_impl(port, ptr, rust_vec_len, data_len), +99 => wire__crate__bridge__api__rust_api_register_home_widget_impl(port, ptr, rust_vec_len, data_len), +100 => wire__crate__bridge__api__rust_api_register_passwordless_notification_impl(port, ptr, rust_vec_len, data_len), +101 => wire__crate__bridge__api__rust_api_register_passwordless_recovery_impl(port, ptr, rust_vec_len, data_len), +102 => wire__crate__bridge__api__rust_api_reload_configuration_impl(port, ptr, rust_vec_len, data_len), +103 => wire__crate__bridge__api__rust_api_remove_additional_user_impl(port, ptr, rust_vec_len, data_len), +104 => wire__crate__bridge__api__rust_api_remove_custom_avatar_impl(port, ptr, rust_vec_len, data_len), +105 => wire__crate__bridge__api__rust_api_remove_media_files_impl(port, ptr, rust_vec_len, data_len), +106 => wire__crate__bridge__api__rust_api_report_user_impl(port, ptr, rust_vec_len, data_len), +107 => wire__crate__bridge__api__rust_api_request_binary_impl(port, ptr, rust_vec_len, data_len), +108 => wire__crate__bridge__api__rust_api_request_contact_by_username_impl(port, ptr, rust_vec_len, data_len), +109 => wire__crate__bridge__api__rust_api_request_media_reupload_impl(port, ptr, rust_vec_len, data_len), +110 => wire__crate__bridge__api__rust_api_request_memories_upload_impl(port, ptr, rust_vec_len, data_len), +111 => wire__crate__bridge__api__rust_api_retransmit_all_messages_impl(port, ptr, rust_vec_len, data_len), +112 => wire__crate__bridge__api__rust_api_retry_pending_media_reuploads_impl(port, ptr, rust_vec_len, data_len), +113 => wire__crate__bridge__api__rust_api_reupload_pending_media_impl(port, ptr, rust_vec_len, data_len), +114 => wire__crate__bridge__api__rust_api_save_media_to_gallery_impl(port, ptr, rust_vec_len, data_len), +115 => wire__crate__bridge__api__rust_api_send_binary_impl(port, ptr, rust_vec_len, data_len), +116 => wire__crate__bridge__api__rust_api_send_contact_profile_impl(port, ptr, rust_vec_len, data_len), +117 => wire__crate__bridge__api__rust_api_send_encrypted_content_impl(port, ptr, rust_vec_len, data_len), +118 => wire__crate__bridge__api__rust_api_send_encrypted_content_to_group_impl(port, ptr, rust_vec_len, data_len), +119 => wire__crate__bridge__api__rust_api_send_media_to_groups_impl(port, ptr, rust_vec_len, data_len), +120 => wire__crate__bridge__api__rust_api_send_queued_message_impl(port, ptr, rust_vec_len, data_len), +121 => wire__crate__bridge__api__rust_api_send_text_message_impl(port, ptr, rust_vec_len, data_len), +122 => wire__crate__bridge__api__rust_api_send_typing_impl(port, ptr, rust_vec_len, data_len), +123 => wire__crate__bridge__api__rust_api_set_background_impl(port, ptr, rust_vec_len, data_len), +124 => wire__crate__bridge__api__rust_api_set_custom_avatar_audience_impl(port, ptr, rust_vec_len, data_len), +125 => wire__crate__bridge__api__rust_api_set_home_widget_groups_impl(port, ptr, rust_vec_len, data_len), +126 => wire__crate__bridge__api__rust_api_set_login_token_impl(port, ptr, rust_vec_len, data_len), +127 => wire__crate__bridge__api__rust_api_set_media_display_limit_impl(port, ptr, rust_vec_len, data_len), +128 => wire__crate__bridge__api__rust_api_set_media_requires_authentication_impl(port, ptr, rust_vec_len, data_len), +129 => wire__crate__bridge__api__rust_api_set_media_trim_impl(port, ptr, rust_vec_len, data_len), +130 => wire__crate__bridge__api__rust_api_set_network_available_impl(port, ptr, rust_vec_len, data_len), +131 => wire__crate__bridge__api__rust_api_store_media_impl(port, ptr, rust_vec_len, data_len), +132 => wire__crate__bridge__api__rust_api_submit_recovery_share_impl(port, ptr, rust_vec_len, data_len), +133 => wire__crate__bridge__api__rust_api_sync_widget_permissions_impl(port, ptr, rust_vec_len, data_len), +134 => wire__crate__bridge__api__rust_api_toggle_media_remove_audio_impl(port, ptr, rust_vec_len, data_len), +135 => wire__crate__bridge__api__rust_api_try_request_contact_by_id_impl(port, ptr, rust_vec_len, data_len), +136 => wire__crate__bridge__api__rust_api_unregister_home_widget_impl(port, ptr, rust_vec_len, data_len), +137 => wire__crate__bridge__api__rust_api_update_fcm_token_impl(port, ptr, rust_vec_len, data_len), +138 => wire__crate__bridge__api__rust_api_update_signed_pre_key_impl(port, ptr, rust_vec_len, data_len), +139 => wire__crate__bridge__api__rust_api_upload_pqc_pre_keys_impl(port, ptr, rust_vec_len, data_len), +140 => wire__crate__bridge__wrapper__app_database__rust_app_database_changes_impl(port, ptr, rust_vec_len, data_len), +141 => wire__crate__bridge__wrapper__app_database__rust_app_database_execute_impl(port, ptr, rust_vec_len, data_len), +142 => wire__crate__bridge__wrapper__app_database__rust_app_database_legacy_import_complete_impl(port, ptr, rust_vec_len, data_len), +143 => wire__crate__bridge__wrapper__app_database__rust_app_database_migrate_legacy_database_impl(port, ptr, rust_vec_len, data_len), +144 => wire__crate__bridge__wrapper__app_database__rust_app_database_select_impl(port, ptr, rust_vec_len, data_len), +145 => wire__crate__bridge__wrapper__backup__rust_backup_archive_create_backup_archive_impl(port, ptr, rust_vec_len, data_len), +146 => wire__crate__bridge__wrapper__backup__rust_backup_archive_get_backup_download_token_impl(port, ptr, rust_vec_len, data_len), +147 => wire__crate__bridge__wrapper__backup__rust_backup_archive_restore_backup_archive_impl(port, ptr, rust_vec_len, data_len), +148 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_id_impl(port, ptr, rust_vec_len, data_len), +149 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +150 => wire__crate__bridge__wrapper__backup__rust_backup_identity_get_identity_backup_bytes_impl(port, ptr, rust_vec_len, data_len), +151 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +152 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len), +153 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len), +154 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), +155 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len), +156 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len), +157 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len), +158 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len), +159 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len), +160 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_local_credentials_impl(port, ptr, rust_vec_len, data_len), +161 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len), +162 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len), +163 => wire__crate__bridge__wrapper__signal__rust_signal_decrypt_impl(port, ptr, rust_vec_len, data_len), +164 => wire__crate__bridge__wrapper__signal__rust_signal_encrypt_impl(port, ptr, rust_vec_len, data_len), +165 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len), +166 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len), +167 => wire__crate__bridge__wrapper__signal__rust_signal_get_contact_public_key_impl(port, ptr, rust_vec_len, data_len), +168 => wire__crate__bridge__wrapper__signal__rust_signal_get_safety_number_impl(port, ptr, rust_vec_len, data_len), +169 => wire__crate__bridge__wrapper__signal__rust_signal_get_user_public_key_impl(port, ptr, rust_vec_len, data_len), +170 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len), +171 => wire__crate__bridge__wrapper__signal__rust_signal_reset_contact_session_impl(port, ptr, rust_vec_len, data_len), +172 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len), +173 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len), +174 => wire__crate__bridge__webxdc__send_update_impl(port, ptr, rust_vec_len, data_len), +175 => wire__crate__bridge__webxdc__serve_impl(port, ptr, rust_vec_len, data_len), +176 => wire__crate__bridge__stories__story_audience_chats_impl(port, ptr, rust_vec_len, data_len), +177 => wire__crate__bridge__groups__update_chat_deletion_time_impl(port, ptr, rust_vec_len, data_len), +178 => wire__crate__bridge__groups__update_group_name_impl(port, ptr, rust_vec_len, data_len), +179 => wire__crate__bridge__webxdc__updates_after_impl(port, ptr, rust_vec_len, data_len), +181 => wire__crate__bridge__user_config__user_config_api_create_impl(port, ptr, rust_vec_len, data_len), +182 => wire__crate__bridge__user_config__user_config_api_import_json_impl(port, ptr, rust_vec_len, data_len), +183 => wire__crate__bridge__user_config__user_config_api_load_impl(port, ptr, rust_vec_len, data_len), +184 => wire__crate__bridge__user_config__user_config_api_save_impl(port, ptr, rust_vec_len, data_len), +185 => wire__crate__bridge__user_config__user_config_api_update_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -8388,15 +8562,15 @@ fn pde_ffi_dispatcher_sync_impl( 28 => wire__crate__bridge__stories__max_media_recipients_impl(ptr, rust_vec_len, data_len), 40 => wire__crate__bridge__api__rust_api_api_base_url_impl(ptr, rust_vec_len, data_len), 42 => wire__crate__bridge__api__rust_api_avatar_png_path_impl(ptr, rust_vec_len, data_len), - 55 => { + 56 => { wire__crate__bridge__api__rust_api_decode_avatar_svg_impl(ptr, rust_vec_len, data_len) } - 176 => wire__crate__bridge__user_config__user_config_api_clone_impl( + 180 => wire__crate__bridge__user_config__user_config_api_clone_impl( ptr, rust_vec_len, data_len, ), - 182 => wire__crate__bridge__logging__write_log_impl(ptr, rust_vec_len, data_len), + 186 => wire__crate__bridge__logging__write_log_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -8498,6 +8672,31 @@ impl flutter_rust_bridge::IntoIntoDart flutter_rust_bridge::for_generated::DartAbi { + [ + self.webp.into_into_dart().into_dart(), + self.sha256.into_into_dart().into_dart(), + self.width.into_into_dart().into_dart(), + self.height.into_into_dart().into_dart(), + self.accepted_contacts_only.into_into_dart().into_dart(), + self.source_revision.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::services::avatars::CustomAvatarInfo +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::services::avatars::CustomAvatarInfo +{ + fn into_into_dart(self) -> crate::services::avatars::CustomAvatarInfo { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for FrbWrapper { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -9677,6 +9876,18 @@ impl SseEncode for bool { } } +impl SseEncode for crate::services::avatars::CustomAvatarInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(self.webp, serializer); + >>::sse_encode(self.sha256, serializer); + >::sse_encode(self.width, serializer); + >::sse_encode(self.height, serializer); + ::sse_encode(self.accepted_contacts_only, serializer); + ::sse_encode(self.source_revision, serializer); + } +} + impl SseEncode for f64 { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { diff --git a/rust/src/services/avatars.rs b/rust/src/services/avatars.rs index ac8f1eac..c17e9e49 100644 --- a/rust/src/services/avatars.rs +++ b/rust/src/services/avatars.rs @@ -2,10 +2,18 @@ use crate::context::Context; use crate::error::{Result, TwonlyError}; use crate::user_config::UserConfig; use flate2::read::GzDecoder; -use std::io::Read; +use image::imageops; +use image::{DynamicImage, ImageDecoder, Rgba, RgbaImage}; +use prost::Message as _; +use sha2::{Digest, Sha256}; +use sqlx::{Row, Sqlite, Transaction}; +use std::io::{Cursor, Read}; use std::path::{Path, PathBuf}; use std::sync::Mutex; +use crate::api::proto::client::encrypted_content; +use crate::services::{media_codec, stickers}; + const DEFAULT_AVATAR_SVG: &str = r##" @@ -36,6 +44,367 @@ const DEFAULT_AVATAR_SVG: &str = r##"