fix callback issue and increase minimum threshold

This commit is contained in:
otsmr 2026-06-07 12:10:10 +02:00
parent da97fe5f3d
commit d1eb5d5be6
17 changed files with 243 additions and 96 deletions

View file

@ -1,5 +1,9 @@
# Changelog
## 0.2.30
- Fix: Changed minimum threshold for the user discovery to 3
## 0.2.28
- Improved: Design of some UI components

View file

@ -9,8 +9,10 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These functions are ignored because they are not marked as `pub`: `get_callbacks`
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `FlutterCallbacks`, `Logging`, `UserDiscoveryCallbacks`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`
Future<void> initFlutterCallbacks({
required int callbackId,
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
required FutureOr<Uint8List?> Function(Uint8List) userDiscoverySignData,
required FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
@ -39,6 +41,7 @@ Future<void> initFlutterCallbacks({
required FutureOr<Uint8List?> Function(PlatformInt64)
userDiscoveryGetContactPromotion,
}) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks(
callbackId: callbackId,
loggingGetStreamSink: loggingGetStreamSink,
userDiscoverySignData: userDiscoverySignData,
userDiscoveryVerifySignature: userDiscoveryVerifySignature,

View file

@ -9,36 +9,45 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
class FlutterUserDiscovery {
const FlutterUserDiscovery();
static Future<Uint8List> getCurrentVersion() => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion();
static Future<Uint8List> getCurrentVersion({required int callbackId}) =>
RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion(
callbackId: callbackId,
);
static Future<List<Uint8List>> getNewMessages({
required int callbackId,
required PlatformInt64 contactId,
required List<int> receivedVersion,
}) => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages(
callbackId: callbackId,
contactId: contactId,
receivedVersion: receivedVersion,
);
static Future<void> handleNewMessages({
required int callbackId,
required PlatformInt64 contactId,
PlatformInt64? publicKeyVerifiedTimestamp,
required List<Uint8List> messages,
}) => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages(
callbackId: callbackId,
contactId: contactId,
publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp,
messages: messages,
);
static Future<void> initializeOrUpdate({
required int callbackId,
required int threshold,
required PlatformInt64 userId,
required List<int> publicKey,
required bool sharePromotion,
}) => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate(
callbackId: callbackId,
threshold: threshold,
userId: userId,
publicKey: publicKey,
@ -46,19 +55,23 @@ class FlutterUserDiscovery {
);
static Future<Uint8List?> shouldRequestNewMessages({
required int callbackId,
required PlatformInt64 contactId,
required List<int> version,
}) => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages(
callbackId: callbackId,
contactId: contactId,
version: version,
);
static Future<void> updateVerificationStateForUser({
required int callbackId,
required PlatformInt64 contactId,
PlatformInt64? publicKeyVerifiedTimestamp,
}) => RustLib.instance.api
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser(
callbackId: callbackId,
contactId: contactId,
publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp,
);

View file

@ -87,16 +87,20 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
abstract class RustLibApi extends BaseApi {
Future<Uint8List>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion();
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion({
required int callbackId,
});
Future<List<Uint8List>>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages({
required int callbackId,
required PlatformInt64 contactId,
required List<int> receivedVersion,
});
Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages({
required int callbackId,
required PlatformInt64 contactId,
PlatformInt64? publicKeyVerifiedTimestamp,
required List<Uint8List> messages,
@ -104,6 +108,7 @@ abstract class RustLibApi extends BaseApi {
Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate({
required int callbackId,
required int threshold,
required PlatformInt64 userId,
required List<int> publicKey,
@ -112,17 +117,20 @@ abstract class RustLibApi extends BaseApi {
Future<Uint8List?>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({
required int callbackId,
required PlatformInt64 contactId,
required List<int> version,
});
Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser({
required int callbackId,
required PlatformInt64 contactId,
PlatformInt64? publicKeyVerifiedTimestamp,
});
Future<void> crateBridgeCallbacksInitFlutterCallbacks({
required int callbackId,
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
required FutureOr<Uint8List?> Function(Uint8List) userDiscoverySignData,
required FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
@ -242,11 +250,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
@override
Future<Uint8List>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion() {
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion({
required int callbackId,
}) {
return handler.executeNormal(
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
pdeCallFfi(
generalizedFrbRustBinding,
serializer,
@ -260,7 +271,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersionConstMeta,
argValues: [],
argValues: [callbackId],
apiImpl: this,
),
);
@ -270,12 +281,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersionConstMeta =>
const TaskConstMeta(
debugName: "flutter_user_discovery_get_current_version",
argNames: [],
argNames: ["callbackId"],
);
@override
Future<List<Uint8List>>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages({
required int callbackId,
required PlatformInt64 contactId,
required List<int> receivedVersion,
}) {
@ -283,6 +295,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
sse_encode_i_64(contactId, serializer);
sse_encode_list_prim_u_8_loose(receivedVersion, serializer);
pdeCallFfi(
@ -298,7 +311,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessagesConstMeta,
argValues: [contactId, receivedVersion],
argValues: [callbackId, contactId, receivedVersion],
apiImpl: this,
),
);
@ -308,12 +321,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessagesConstMeta =>
const TaskConstMeta(
debugName: "flutter_user_discovery_get_new_messages",
argNames: ["contactId", "receivedVersion"],
argNames: ["callbackId", "contactId", "receivedVersion"],
);
@override
Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages({
required int callbackId,
required PlatformInt64 contactId,
PlatformInt64? publicKeyVerifiedTimestamp,
required List<Uint8List> messages,
@ -322,6 +336,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
sse_encode_i_64(contactId, serializer);
sse_encode_opt_box_autoadd_i_64(
publicKeyVerifiedTimestamp,
@ -341,7 +356,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessagesConstMeta,
argValues: [contactId, publicKeyVerifiedTimestamp, messages],
argValues: [
callbackId,
contactId,
publicKeyVerifiedTimestamp,
messages,
],
apiImpl: this,
),
);
@ -351,12 +371,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessagesConstMeta =>
const TaskConstMeta(
debugName: "flutter_user_discovery_handle_new_messages",
argNames: ["contactId", "publicKeyVerifiedTimestamp", "messages"],
argNames: [
"callbackId",
"contactId",
"publicKeyVerifiedTimestamp",
"messages",
],
);
@override
Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate({
required int callbackId,
required int threshold,
required PlatformInt64 userId,
required List<int> publicKey,
@ -366,6 +392,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
sse_encode_u_8(threshold, serializer);
sse_encode_i_64(userId, serializer);
sse_encode_list_prim_u_8_loose(publicKey, serializer);
@ -383,7 +410,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdateConstMeta,
argValues: [threshold, userId, publicKey, sharePromotion],
argValues: [callbackId, threshold, userId, publicKey, sharePromotion],
apiImpl: this,
),
);
@ -393,12 +420,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdateConstMeta =>
const TaskConstMeta(
debugName: "flutter_user_discovery_initialize_or_update",
argNames: ["threshold", "userId", "publicKey", "sharePromotion"],
argNames: [
"callbackId",
"threshold",
"userId",
"publicKey",
"sharePromotion",
],
);
@override
Future<Uint8List?>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({
required int callbackId,
required PlatformInt64 contactId,
required List<int> version,
}) {
@ -406,6 +440,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
sse_encode_i_64(contactId, serializer);
sse_encode_list_prim_u_8_loose(version, serializer);
pdeCallFfi(
@ -421,7 +456,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessagesConstMeta,
argValues: [contactId, version],
argValues: [callbackId, contactId, version],
apiImpl: this,
),
);
@ -431,12 +466,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessagesConstMeta =>
const TaskConstMeta(
debugName: "flutter_user_discovery_should_request_new_messages",
argNames: ["contactId", "version"],
argNames: ["callbackId", "contactId", "version"],
);
@override
Future<void>
crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser({
required int callbackId,
required PlatformInt64 contactId,
PlatformInt64? publicKeyVerifiedTimestamp,
}) {
@ -444,6 +480,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
sse_encode_i_64(contactId, serializer);
sse_encode_opt_box_autoadd_i_64(
publicKeyVerifiedTimestamp,
@ -462,7 +499,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta:
kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUserConstMeta,
argValues: [contactId, publicKeyVerifiedTimestamp],
argValues: [callbackId, contactId, publicKeyVerifiedTimestamp],
apiImpl: this,
),
);
@ -472,11 +509,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUserConstMeta =>
const TaskConstMeta(
debugName: "flutter_user_discovery_update_verification_state_for_user",
argNames: ["contactId", "publicKeyVerifiedTimestamp"],
argNames: ["callbackId", "contactId", "publicKeyVerifiedTimestamp"],
);
@override
Future<void> crateBridgeCallbacksInitFlutterCallbacks({
required int callbackId,
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
required FutureOr<Uint8List?> Function(Uint8List) userDiscoverySignData,
required FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
@ -513,6 +551,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_u_32(callbackId, serializer);
sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
loggingGetStreamSink,
serializer,
@ -586,6 +625,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
),
constMeta: kCrateBridgeCallbacksInitFlutterCallbacksConstMeta,
argValues: [
callbackId,
loggingGetStreamSink,
userDiscoverySignData,
userDiscoveryVerifySignature,
@ -611,6 +651,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
const TaskConstMeta(
debugName: "init_flutter_callbacks",
argNames: [
"callbackId",
"loggingGetStreamSink",
"userDiscoverySignData",
"userDiscoveryVerifySignature",

View file

@ -1,8 +1,11 @@
import 'dart:async';
import 'dart:math';
import 'package:camera/camera.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/utils/log.dart';
final int isolateCallbackId = Random().nextInt(0x7FFFFFFF);
class AppEnvironment {
static late String cacheDir;
static late String supportDir;

View file

@ -1,9 +1,11 @@
import 'package:twonly/core/bridge/callbacks.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/callbacks/logging.callbacks.dart';
import 'package:twonly/src/callbacks/user_discovery.callbacks.dart';
Future<void> initFlutterCallbacksForRust() async {
await initFlutterCallbacks(
callbackId: isolateCallbackId,
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
userDiscoverySetShares: UserDiscoveryCallbacks.setShares,
userDiscoveryGetShareForContact:

View file

@ -1,6 +1,7 @@
import 'package:clock/clock.dart';
import 'package:drift/drift.dart';
import 'package:twonly/core/bridge/wrapper/user_discovery.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/groups.table.dart';
@ -216,6 +217,7 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
);
if (userService.currentUser.isUserDiscoveryEnabled) {
await FlutterUserDiscovery.updateVerificationStateForUser(
callbackId: isolateCallbackId,
contactId: contactId,
publicKeyVerifiedTimestamp: clock.now().millisecondsSinceEpoch,
);
@ -232,6 +234,7 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
)..where((kv) => kv.contactId.equals(contactId))).go();
if (userService.currentUser.isUserDiscoveryEnabled) {
await FlutterUserDiscovery.updateVerificationStateForUser(
callbackId: isolateCallbackId,
contactId: contactId,
);
}
@ -251,6 +254,7 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
final remaining = await getContactVerification(contactId);
if (remaining.isEmpty && userService.currentUser.isUserDiscoveryEnabled) {
await FlutterUserDiscovery.updateVerificationStateForUser(
callbackId: isolateCallbackId,
contactId: contactId,
);
}

View file

@ -114,8 +114,8 @@ class UserData {
@JsonKey(defaultValue: 4)
int requiredSendImages = 4;
@JsonKey(defaultValue: 2)
int userDiscoveryThreshold = 2;
@JsonKey(defaultValue: 3)
int userDiscoveryThreshold = 3;
@JsonKey(defaultValue: false)
bool userDiscoveryRequiresManualApproval = false;

View file

@ -62,7 +62,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
),
)
..storeMediaFilesInGallery =
json['storeMediaFilesInGallery'] as bool? ?? false
json['storeMediaFilesInGallery'] as bool? ?? true
..autoStoreAllSendUnlimitedMediaFiles =
json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false
..typingIndicators = json['typingIndicators'] as bool? ?? true
@ -78,7 +78,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
json['isUserDiscoveryEnabled'] as bool? ?? false
..requiredSendImages = (json['requiredSendImages'] as num?)?.toInt() ?? 4
..userDiscoveryThreshold =
(json['userDiscoveryThreshold'] as num?)?.toInt() ?? 2
(json['userDiscoveryThreshold'] as num?)?.toInt() ?? 3
..userDiscoveryRequiresManualApproval =
json['userDiscoveryRequiresManualApproval'] as bool? ?? false
..userDiscoverySharePromotion =

View file

@ -14,6 +14,7 @@ import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/signal_identity.model.dart';
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/services/user_discovery.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/secure_storage.dart';
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
@ -144,6 +145,19 @@ Future<void> runMigrations() async {
}
}
if (userService.currentUser.appVersion < 116) {
if (userService.currentUser.userDiscoveryThreshold == 2) {
if (userService.currentUser.isUserDiscoveryEnabled) {
await UserDiscoveryService.initializeOrUpdate(
threshold: 3,
sharePromotion: userService.currentUser.userDiscoverySharePromotion,
);
} else {
await UserService.update((u) => u..userDiscoveryThreshold = 3);
}
}
await UserService.update((u) => u.appVersion = 116);
}
if (kDebugMode) {
assert(
AppState.latestAppVersionId == 116,

View file

@ -84,6 +84,7 @@ class UserDiscoveryService {
final publicKey = await getUserPublicKey();
Log.info('UserDiscoveryService: initializing Rust bridge');
await FlutterUserDiscovery.initializeOrUpdate(
callbackId: isolateCallbackId,
threshold: threshold,
userId: userId,
publicKey: publicKey,
@ -104,7 +105,7 @@ class UserDiscoveryService {
static Future<Uint8List?> getCurrentVersion() async {
try {
return await FlutterUserDiscovery.getCurrentVersion()
return await FlutterUserDiscovery.getCurrentVersion(callbackId: isolateCallbackId)
.timeout(const Duration(seconds: 5));
} catch (e) {
Log.error(e);
@ -139,6 +140,7 @@ class UserDiscoveryService {
) async {
try {
return await FlutterUserDiscovery.shouldRequestNewMessages(
callbackId: isolateCallbackId,
contactId: fromUserId,
version: receivedVersion,
).timeout(const Duration(seconds: 5));
@ -154,6 +156,7 @@ class UserDiscoveryService {
) async {
try {
return await FlutterUserDiscovery.getNewMessages(
callbackId: isolateCallbackId,
contactId: fromUserId,
receivedVersion: receivedVersion,
).timeout(const Duration(seconds: 5));
@ -172,6 +175,7 @@ class UserDiscoveryService {
.getContactVerification(fromUserId);
return await FlutterUserDiscovery.handleNewMessages(
callbackId: isolateCallbackId,
contactId: fromUserId,
messages: messages,
publicKeyVerifiedTimestamp:

View file

@ -108,7 +108,8 @@ class UserDiscoverySetupComp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final showShareYourFriends =
showOnlySpecificPage == UserDiscoveryPages.all || showOnlySpecificPage == UserDiscoveryPages.shareYourFriends;
showOnlySpecificPage == UserDiscoveryPages.all ||
showOnlySpecificPage == UserDiscoveryPages.shareYourFriends;
final showLetYourFriendsFindYou =
showOnlySpecificPage == UserDiscoveryPages.all ||
showOnlySpecificPage == UserDiscoveryPages.letYourFriendsFindYou;
@ -336,7 +337,9 @@ class UserDiscoverySetupComp extends StatelessWidget {
),
),
subtitle: Text(
context.lang.userDiscoverySettingsManualApprovalDesc,
context
.lang
.userDiscoverySettingsManualApprovalDesc,
style: TextStyle(
fontSize: 11,
color: context.color.onSurfaceVariant,
@ -350,13 +353,16 @@ class UserDiscoverySetupComp extends StatelessWidget {
),
],
),
crossFadeState: state.isUserDiscoveryEnabled ? CrossFadeState.showSecond : CrossFadeState.showFirst,
crossFadeState: state.isUserDiscoveryEnabled
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 300),
),
],
),
),
if (showOnlySpecificPage == UserDiscoveryPages.all) const SizedBox(height: 48),
if (showOnlySpecificPage == UserDiscoveryPages.all)
const SizedBox(height: 48),
],
if (showLetYourFriendsFindYou) ...[
Text(
@ -587,7 +593,9 @@ class UserDiscoverySetupComp extends StatelessWidget {
children: [
Expanded(
child: Text(
context.lang.userDiscoverySettingsMutualFriends,
context
.lang
.userDiscoverySettingsMutualFriends,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -603,7 +611,8 @@ class UserDiscoverySetupComp extends StatelessWidget {
color: context.color.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: context.color.outlineVariant.withValues(
color: context.color.outlineVariant
.withValues(
alpha: 0.5,
),
),
@ -616,9 +625,9 @@ class UserDiscoverySetupComp extends StatelessWidget {
fontWeight: FontWeight.bold,
),
items: List.generate(
9,
8,
(index) {
final value = index + 2;
final value = index + 3;
return DropdownMenuItem<int>(
value: value,
child: Text('$value'),
@ -640,7 +649,9 @@ class UserDiscoverySetupComp extends StatelessWidget {
),
],
),
crossFadeState: state.sharePromotion ? CrossFadeState.showSecond : CrossFadeState.showFirst,
crossFadeState: state.sharePromotion
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 300),
),
],

View file

@ -26,7 +26,7 @@ class _UserDiscoverySettingsViewState extends State<UserDiscoverySettingsView> {
isUserDiscoveryEnabled: u.isUserDiscoveryEnabled,
sharePromotion: u.userDiscoverySharePromotion,
isManualApprovalEnabled: u.userDiscoveryRequiresManualApproval,
threshold: u.userDiscoveryThreshold,
threshold: u.userDiscoveryThreshold < 3 ? 3 : u.userDiscoveryThreshold,
);
}

View file

@ -9,7 +9,13 @@ use crate::error::{Result, TwonlyError};
use crate::{callback_generator, frb_generated::StreamSink};
use std::sync::Arc;
static FLUTTER_CALLBACKS: std::sync::RwLock<Option<FlutterCallbacks>> =
use std::collections::HashMap;
tokio::task_local! {
pub(crate) static CURRENT_CALLBACK_ID: u32;
}
pub(crate) static FLUTTER_CALLBACKS: std::sync::RwLock<Option<HashMap<u32, FlutterCallbacks>>> =
std::sync::RwLock::new(None);
// This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks
@ -41,9 +47,24 @@ callback_generator! {
}
pub(crate) fn get_callbacks() -> Result<FlutterCallbacks> {
FLUTTER_CALLBACKS
.read()
.unwrap()
.clone()
.ok_or(TwonlyError::MissingCallbackInitialization)
let caller_opt = CURRENT_CALLBACK_ID.try_with(|&c| c).ok();
let lock = FLUTTER_CALLBACKS.read().unwrap();
let map = lock.as_ref().ok_or(TwonlyError::MissingCallbackInitialization)?;
if let Some(id) = caller_opt {
if let Some(cb) = map.get(&id) {
return Ok(cb.clone());
}
}
// Fallback: if not in a scoped tokio task or if the specific callback_id isn't found,
// we pick the first available callbacks from the map. This gracefully handles
// tracing initialization which happens outside of any scoped task.
if let Some((_, cb)) = map.iter().next() {
tracing::error!("FlutterCallbacks fallback used: No CURRENT_CALLBACK_ID scope was found, or the ID was missing from the map. Using an arbitrary callback. This may lead to race conditions if multiple isolates are active.");
return Ok(cb.clone());
}
Err(TwonlyError::MissingCallbackInitialization)
}

View file

@ -32,6 +32,7 @@ macro_rules! callback_generator {
// 3. Generate the Automated Init Function
paste::paste! {
pub fn init_flutter_callbacks(
callback_id: u32,
$(
$(
// Parameters: sub-struct_field + _ + fn_name
@ -49,9 +50,11 @@ macro_rules! callback_generator {
)*
};
// Use the static global strictly named FLUTTER_CALLBACKS
let mut lock = FLUTTER_CALLBACKS.write().unwrap();
*lock = Some(callbacks);
if lock.is_none() {
*lock = Some(std::collections::HashMap::new());
}
lock.as_mut().unwrap().insert(callback_id, callbacks);
}
}
};

View file

@ -1,3 +1,4 @@
use crate::bridge::callbacks::CURRENT_CALLBACK_ID;
use crate::bridge::get_twonly_flutter;
use crate::error::Result;
@ -5,11 +6,13 @@ pub struct FlutterUserDiscovery {}
impl FlutterUserDiscovery {
pub async fn initialize_or_update(
callback_id: u32,
threshold: u8,
user_id: i64,
public_key: Vec<u8>,
share_promotion: bool,
) -> Result<()> {
CURRENT_CALLBACK_ID.scope(callback_id, async move {
tracing::info!("Rust bridge: initialize_or_update started");
let twonly = get_twonly_flutter()?;
tracing::info!("Rust bridge: getting user_discovery lock");
@ -20,63 +23,78 @@ impl FlutterUserDiscovery {
.await;
tracing::info!("Rust bridge: initialize_or_update on protocols finished");
Ok(res?)
}).await
}
pub async fn get_current_version() -> Result<Vec<u8>> {
pub async fn get_current_version(callback_id: u32) -> Result<Vec<u8>> {
CURRENT_CALLBACK_ID.scope(callback_id, async move {
Ok(get_twonly_flutter()?
.user_discovery
.get()
.await
.get_current_version()
.await?)
}).await
}
pub async fn get_new_messages(
callback_id: u32,
contact_id: i64,
received_version: &[u8],
) -> Result<Vec<Vec<u8>>> {
CURRENT_CALLBACK_ID.scope(callback_id, async move {
Ok(get_twonly_flutter()?
.user_discovery
.get()
.await
.get_new_messages(contact_id, received_version)
.await?)
}).await
}
pub async fn should_request_new_messages(
callback_id: u32,
contact_id: i64,
version: &[u8],
) -> Result<Option<Vec<u8>>> {
CURRENT_CALLBACK_ID.scope(callback_id, async move {
Ok(get_twonly_flutter()?
.user_discovery
.get()
.await
.should_request_new_messages(contact_id, version)
.await?)
}).await
}
pub async fn handle_new_messages(
callback_id: u32,
contact_id: i64,
public_key_verified_timestamp: Option<i64>,
messages: Vec<Vec<u8>>,
) -> Result<()> {
CURRENT_CALLBACK_ID.scope(callback_id, async move {
Ok(get_twonly_flutter()?
.user_discovery
.get()
.await
.handle_new_messages(contact_id, public_key_verified_timestamp, messages)
.await?)
}).await
}
pub async fn update_verification_state_for_user(
callback_id: u32,
contact_id: i64,
public_key_verified_timestamp: Option<i64>,
) -> Result<()> {
CURRENT_CALLBACK_ID.scope(callback_id, async move {
Ok(get_twonly_flutter()?
.user_discovery
.get()
.await
.update_verification_state_for_user(contact_id, public_key_verified_timestamp)
.await?)
}).await
}
}

View file

@ -55,9 +55,9 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_curr
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_get_current_version", 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 {
let api_callback_id = <u32>::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::wrapper::user_discovery::FlutterUserDiscovery::get_current_version().await?; Ok(output_ok)
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_current_version(api_callback_id).await?; Ok(output_ok)
})().await)
} })
}
@ -70,10 +70,11 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_get_new_messages", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_callback_id = <u32>::sse_decode(&mut deserializer);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_received_version = <Vec<u8>>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_new_messages(api_contact_id, &api_received_version).await?; Ok(output_ok)
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_new_messages(api_callback_id, api_contact_id, &api_received_version).await?; Ok(output_ok)
})().await)
} })
}
@ -86,11 +87,12 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_n
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_handle_new_messages", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_callback_id = <u32>::sse_decode(&mut deserializer);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_public_key_verified_timestamp = <Option<i64>>::sse_decode(&mut deserializer);
let api_messages = <Vec<Vec<u8>>>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::handle_new_messages(api_contact_id, api_public_key_verified_timestamp, api_messages).await?; Ok(output_ok)
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::handle_new_messages(api_callback_id, api_contact_id, api_public_key_verified_timestamp, api_messages).await?; Ok(output_ok)
})().await)
} })
}
@ -103,12 +105,13 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initiali
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_initialize_or_update", 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_threshold = <u8>::sse_decode(&mut deserializer);
let api_callback_id = <u32>::sse_decode(&mut deserializer);
let api_threshold = <u8>::sse_decode(&mut deserializer);
let api_user_id = <i64>::sse_decode(&mut deserializer);
let api_public_key = <Vec<u8>>::sse_decode(&mut deserializer);
let api_share_promotion = <bool>::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::wrapper::user_discovery::FlutterUserDiscovery::initialize_or_update(api_threshold, api_user_id, api_public_key, api_share_promotion).await?; Ok(output_ok)
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::initialize_or_update(api_callback_id, api_threshold, api_user_id, api_public_key, api_share_promotion).await?; Ok(output_ok)
})().await)
} })
}
@ -121,10 +124,11 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_r
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_should_request_new_messages", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_callback_id = <u32>::sse_decode(&mut deserializer);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_version = <Vec<u8>>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::should_request_new_messages(api_contact_id, &api_version).await?; Ok(output_ok)
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::should_request_new_messages(api_callback_id, api_contact_id, &api_version).await?; Ok(output_ok)
})().await)
} })
}
@ -137,10 +141,11 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_update_v
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_update_verification_state_for_user", 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_contact_id = <i64>::sse_decode(&mut deserializer);
let api_callback_id = <u32>::sse_decode(&mut deserializer);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_public_key_verified_timestamp = <Option<i64>>::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::wrapper::user_discovery::FlutterUserDiscovery::update_verification_state_for_user(api_contact_id, api_public_key_verified_timestamp).await?; Ok(output_ok)
let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::update_verification_state_for_user(api_callback_id, api_contact_id, api_public_key_verified_timestamp).await?; Ok(output_ok)
})().await)
} })
}
@ -153,7 +158,8 @@ fn wire__crate__bridge__callbacks__init_flutter_callbacks_impl(
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "init_flutter_callbacks", 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_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_callback_id = <u32>::sse_decode(&mut deserializer);
let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_sign_data = decode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_verify_signature = decode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_verify_stored_pubkey = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
@ -169,7 +175,7 @@ let api_user_discovery_set_contact_version = decode_DartFn_Inputs_i_64_list_prim
let api_user_discovery_push_new_user_relation = decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
let api_user_discovery_get_contact_promotion = decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));deserializer.end(); move |context| {
transform_result_sse::<_, ()>((move || {
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_logging_get_stream_sink, api_user_discovery_sign_data, api_user_discovery_verify_signature, api_user_discovery_verify_stored_pubkey, api_user_discovery_set_shares, api_user_discovery_get_share_for_contact, api_user_discovery_push_own_promotion_and_clear_old_version, api_user_discovery_get_own_promotions_after_version, api_user_discovery_store_other_promotion, api_user_discovery_get_other_promotions_by_public_id, api_user_discovery_get_announced_user_by_public_id, api_user_discovery_get_contact_version, api_user_discovery_set_contact_version, api_user_discovery_push_new_user_relation, api_user_discovery_get_contact_promotion); })?; Ok(output_ok)
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_callback_id, api_logging_get_stream_sink, api_user_discovery_sign_data, api_user_discovery_verify_signature, api_user_discovery_verify_stored_pubkey, api_user_discovery_set_shares, api_user_discovery_get_share_for_contact, api_user_discovery_push_own_promotion_and_clear_old_version, api_user_discovery_get_own_promotions_after_version, api_user_discovery_store_other_promotion, api_user_discovery_get_other_promotions_by_public_id, api_user_discovery_get_announced_user_by_public_id, api_user_discovery_get_contact_version, api_user_discovery_set_contact_version, api_user_discovery_push_new_user_relation, api_user_discovery_get_contact_promotion); })?; Ok(output_ok)
})())
} })
}