mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 02:32:11 +00:00
initialization of user discovery works
This commit is contained in:
parent
6516c4564c
commit
e1956c9807
47 changed files with 4435 additions and 884 deletions
|
|
@ -5,20 +5,36 @@
|
|||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
import 'database/contact.dart';
|
||||
import 'frb_generated.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `get_workspace`
|
||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Twonly`
|
||||
// These functions are ignored because they are not marked as `pub`: `get_twonly_flutter`
|
||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `TwonlyFlutter`
|
||||
|
||||
Future<void> initializeTwonly({required TwonlyConfig config}) =>
|
||||
RustLib.instance.api.crateBridgeInitializeTwonly(config: config);
|
||||
Future<void> initializeTwonlyFlutter({required TwonlyConfig config}) =>
|
||||
RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config);
|
||||
|
||||
Future<List<Contact>> getAllContacts() =>
|
||||
RustLib.instance.api.crateBridgeGetAllContacts();
|
||||
class AnnouncedUser {
|
||||
const AnnouncedUser({
|
||||
required this.userId,
|
||||
required this.publicKey,
|
||||
required this.publicId,
|
||||
});
|
||||
final PlatformInt64 userId;
|
||||
final Uint8List publicKey;
|
||||
final PlatformInt64 publicId;
|
||||
|
||||
Future<OtherPromotion> loadPromotions() =>
|
||||
RustLib.instance.api.crateBridgeLoadPromotions();
|
||||
@override
|
||||
int get hashCode => userId.hashCode ^ publicKey.hashCode ^ publicId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is AnnouncedUser &&
|
||||
runtimeType == other.runtimeType &&
|
||||
userId == other.userId &&
|
||||
publicKey == other.publicKey &&
|
||||
publicId == other.publicId;
|
||||
}
|
||||
|
||||
class OtherPromotion {
|
||||
const OtherPromotion({
|
||||
|
|
|
|||
58
lib/core/bridge/callbacks.dart
Normal file
58
lib/core/bridge/callbacks.dart
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// 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
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
import '../bridge.dart';
|
||||
import '../frb_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`
|
||||
|
||||
Future<void> initFlutterCallbacks({
|
||||
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
|
||||
required FutureOr<Uint8List?> Function(Uint8List) userDiscoverySignData,
|
||||
required FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
|
||||
userDiscoveryVerifySignature,
|
||||
required FutureOr<bool> Function(PlatformInt64, Uint8List)
|
||||
userDiscoveryVerifyStoredPubkey,
|
||||
required FutureOr<bool> Function(List<Uint8List>) userDiscoverySetShares,
|
||||
required FutureOr<Uint8List?> Function(PlatformInt64)
|
||||
userDiscoveryGetShareForContact,
|
||||
required FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
|
||||
userDiscoveryPushOwnPromotion,
|
||||
required FutureOr<List<Uint8List>?> Function(PlatformInt64)
|
||||
userDiscoveryGetOwnPromotionsAfterVersion,
|
||||
required FutureOr<bool> Function(OtherPromotion)
|
||||
userDiscoveryStoreOtherPromotion,
|
||||
required FutureOr<List<OtherPromotion>?> Function(PlatformInt64)
|
||||
userDiscoveryGetOtherPromotionsByPublicId,
|
||||
required FutureOr<AnnouncedUser?> Function(PlatformInt64)
|
||||
userDiscoveryGetAnnouncedUserByPublicId,
|
||||
required FutureOr<Uint8List?> Function(PlatformInt64)
|
||||
userDiscoveryGetContactVersion,
|
||||
required FutureOr<bool> Function(PlatformInt64, Uint8List)
|
||||
userDiscoverySetContactVersion,
|
||||
required FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?)
|
||||
userDiscoveryPushNewUserRelation,
|
||||
}) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks(
|
||||
loggingGetStreamSink: loggingGetStreamSink,
|
||||
userDiscoverySignData: userDiscoverySignData,
|
||||
userDiscoveryVerifySignature: userDiscoveryVerifySignature,
|
||||
userDiscoveryVerifyStoredPubkey: userDiscoveryVerifyStoredPubkey,
|
||||
userDiscoverySetShares: userDiscoverySetShares,
|
||||
userDiscoveryGetShareForContact: userDiscoveryGetShareForContact,
|
||||
userDiscoveryPushOwnPromotion: userDiscoveryPushOwnPromotion,
|
||||
userDiscoveryGetOwnPromotionsAfterVersion:
|
||||
userDiscoveryGetOwnPromotionsAfterVersion,
|
||||
userDiscoveryStoreOtherPromotion: userDiscoveryStoreOtherPromotion,
|
||||
userDiscoveryGetOtherPromotionsByPublicId:
|
||||
userDiscoveryGetOtherPromotionsByPublicId,
|
||||
userDiscoveryGetAnnouncedUserByPublicId:
|
||||
userDiscoveryGetAnnouncedUserByPublicId,
|
||||
userDiscoveryGetContactVersion: userDiscoveryGetContactVersion,
|
||||
userDiscoverySetContactVersion: userDiscoverySetContactVersion,
|
||||
userDiscoveryPushNewUserRelation: userDiscoveryPushNewUserRelation,
|
||||
);
|
||||
61
lib/core/bridge/wrapper/user_discovery.dart
Normal file
61
lib/core/bridge/wrapper/user_discovery.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// 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
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
import '../../frb_generated.dart';
|
||||
|
||||
class FlutterUserDiscovery {
|
||||
const FlutterUserDiscovery();
|
||||
|
||||
static Future<Uint8List> getCurrentVersion() => RustLib.instance.api
|
||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion();
|
||||
|
||||
static Future<List<Uint8List>> getNewMessages({
|
||||
required PlatformInt64 contactId,
|
||||
required List<int> receivedVersion,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages(
|
||||
contactId: contactId,
|
||||
receivedVersion: receivedVersion,
|
||||
);
|
||||
|
||||
static Future<void> handleUserDiscoveryMessages({
|
||||
required PlatformInt64 contactId,
|
||||
required List<Uint8List> messages,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleUserDiscoveryMessages(
|
||||
contactId: contactId,
|
||||
messages: messages,
|
||||
);
|
||||
|
||||
static Future<void> initializeOrUpdate({
|
||||
required int threshold,
|
||||
required PlatformInt64 userId,
|
||||
required List<int> publicKey,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate(
|
||||
threshold: threshold,
|
||||
userId: userId,
|
||||
publicKey: publicKey,
|
||||
);
|
||||
|
||||
static Future<bool> shouldRequestNewMessages({
|
||||
required PlatformInt64 contactId,
|
||||
required List<int> version,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages(
|
||||
contactId: contactId,
|
||||
version: version,
|
||||
);
|
||||
|
||||
@override
|
||||
int get hashCode => 0;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FlutterUserDiscovery && runtimeType == other.runtimeType;
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
import '../frb_generated.dart';
|
||||
|
||||
class Contact {
|
||||
const Contact({
|
||||
required this.userId,
|
||||
required this.username,
|
||||
});
|
||||
final PlatformInt64 userId;
|
||||
final String username;
|
||||
|
||||
@override
|
||||
int get hashCode => userId.hashCode ^ username.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Contact &&
|
||||
runtimeType == other.runtimeType &&
|
||||
userId == other.userId &&
|
||||
username == other.username;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: unused_import, non_constant_identifier_names, unused_field
|
||||
// ignore_for_file: unused_import, unnecessary_import, non_constant_identifier_names, unused_field
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -10,7 +10,8 @@ import 'dart:ffi' as ffi;
|
|||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||
|
||||
import 'bridge.dart';
|
||||
import 'database/contact.dart';
|
||||
import 'bridge/callbacks.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'frb_generated.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
|
|
@ -24,9 +25,96 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<RustStreamSink<String>> Function()
|
||||
dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<AnnouncedUser?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<List<Uint8List>?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<Uint8List?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?)
|
||||
dco_decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
|
||||
dco_decode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(PlatformInt64, Uint8List)
|
||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(List<Uint8List>)
|
||||
dco_decode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<Uint8List?> Function(Uint8List)
|
||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
|
||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(OtherPromotion)
|
||||
dco_decode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
Object dco_decode_DartOpaque(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
String dco_decode_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnnouncedUser dco_decode_announced_user(dynamic raw);
|
||||
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnnouncedUser dco_decode_box_autoadd_announced_user(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -34,20 +122,41 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
Contact dco_decode_contact(dynamic raw);
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Contact> dco_decode_list_contact(dynamic raw);
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion> dco_decode_list_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion>? dco_decode_opt_list_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
||||
|
||||
|
|
@ -63,12 +172,34 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
void dco_decode_unit(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_usize(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
String sse_decode_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
AnnouncedUser sse_decode_box_autoadd_announced_user(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -78,20 +209,53 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
Contact sse_decode_contact(SseDeserializer deserializer);
|
||||
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Contact> sse_decode_list_contact(SseDeserializer deserializer);
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion> sse_decode_list_other_promotion(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
AnnouncedUser? sse_decode_opt_box_autoadd_announced_user(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion>? sse_decode_opt_list_other_promotion(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -108,10 +272,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
void sse_decode_unit(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
BigInt sse_decode_usize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_AnyhowException(
|
||||
|
|
@ -119,9 +283,112 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
||||
FutureOr<RustStreamSink<String>> Function() self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
||||
FutureOr<AnnouncedUser?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
||||
FutureOr<List<Uint8List>?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
FutureOr<Uint8List?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(PlatformInt64, Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(List<Uint8List>) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
FutureOr<Uint8List?> Function(Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(OtherPromotion) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartOpaque(Object self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_String_Sse(
|
||||
RustStreamSink<String> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_String(String self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_announced_user(AnnouncedUser self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_announced_user(
|
||||
AnnouncedUser self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -135,13 +402,31 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_contact(Contact self, SseSerializer serializer);
|
||||
void sse_encode_flutter_user_discovery(
|
||||
FlutterUserDiscovery self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_contact(List<Contact> self, SseSerializer serializer);
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_other_promotion(
|
||||
List<OtherPromotion> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_strict(
|
||||
|
|
@ -149,12 +434,36 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_announced_user(
|
||||
AnnouncedUser? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_i_64(
|
||||
PlatformInt64? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_other_promotion(
|
||||
List<OtherPromotion>? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_prim_u_8_strict(
|
||||
Uint8List? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_other_promotion(
|
||||
OtherPromotion self,
|
||||
|
|
@ -174,10 +483,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
void sse_encode_unit(void self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
}
|
||||
|
||||
// Section: wire_class
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: unused_import, non_constant_identifier_names
|
||||
// ignore_for_file: unused_import, unnecessary_import, non_constant_identifier_names
|
||||
|
||||
// Static analysis wrongly picks the IO variant, thus ignore this
|
||||
|
||||
|
|
@ -11,7 +11,8 @@ import 'dart:convert';
|
|||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
||||
|
||||
import 'bridge.dart';
|
||||
import 'database/contact.dart';
|
||||
import 'bridge/callbacks.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'frb_generated.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
|
|
@ -25,9 +26,96 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
||||
|
||||
@protected
|
||||
FutureOr<RustStreamSink<String>> Function()
|
||||
dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<AnnouncedUser?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<List<Uint8List>?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<Uint8List?> Function(PlatformInt64)
|
||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?)
|
||||
dco_decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
|
||||
dco_decode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(PlatformInt64, Uint8List)
|
||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(List<Uint8List>)
|
||||
dco_decode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<Uint8List?> Function(Uint8List)
|
||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
|
||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
FutureOr<bool> Function(OtherPromotion)
|
||||
dco_decode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
Object dco_decode_DartOpaque(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
||||
|
||||
@protected
|
||||
String dco_decode_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnnouncedUser dco_decode_announced_user(dynamic raw);
|
||||
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnnouncedUser dco_decode_box_autoadd_announced_user(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -35,20 +123,41 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
Contact dco_decode_contact(dynamic raw);
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Contact> dco_decode_list_contact(dynamic raw);
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion> dco_decode_list_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion>? dco_decode_opt_list_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
||||
|
||||
|
|
@ -64,12 +173,34 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
void dco_decode_unit(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_usize(dynamic raw);
|
||||
|
||||
@protected
|
||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
String sse_decode_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
AnnouncedUser sse_decode_box_autoadd_announced_user(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -79,20 +210,53 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
Contact sse_decode_contact(SseDeserializer deserializer);
|
||||
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Contact> sse_decode_list_contact(SseDeserializer deserializer);
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion> sse_decode_list_other_promotion(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
AnnouncedUser? sse_decode_opt_box_autoadd_announced_user(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<OtherPromotion>? sse_decode_opt_list_other_promotion(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -109,10 +273,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
void sse_decode_unit(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
BigInt sse_decode_usize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_AnyhowException(
|
||||
|
|
@ -120,9 +284,112 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
||||
FutureOr<RustStreamSink<String>> Function() self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
||||
FutureOr<AnnouncedUser?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
||||
FutureOr<List<Uint8List>?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
FutureOr<Uint8List?> Function(PlatformInt64) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(PlatformInt64, Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(List<Uint8List>) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
||||
FutureOr<Uint8List?> Function(Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
||||
FutureOr<bool> Function(OtherPromotion) self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_DartOpaque(Object self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_StreamSink_String_Sse(
|
||||
RustStreamSink<String> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_String(String self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_announced_user(AnnouncedUser self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_announced_user(
|
||||
AnnouncedUser self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -136,13 +403,31 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_contact(Contact self, SseSerializer serializer);
|
||||
void sse_encode_flutter_user_discovery(
|
||||
FlutterUserDiscovery self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_contact(List<Contact> self, SseSerializer serializer);
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_other_promotion(
|
||||
List<OtherPromotion> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_strict(
|
||||
|
|
@ -150,12 +435,36 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_announced_user(
|
||||
AnnouncedUser? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_i_64(
|
||||
PlatformInt64? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_other_promotion(
|
||||
List<OtherPromotion>? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_prim_u_8_strict(
|
||||
Uint8List? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_other_promotion(
|
||||
OtherPromotion self,
|
||||
|
|
@ -175,10 +484,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
void sse_encode_unit(void self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
}
|
||||
|
||||
// Section: wire_class
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:twonly/app.dart';
|
|||
import 'package:twonly/core/bridge.dart' as bridge;
|
||||
import 'package:twonly/core/frb_generated.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/callbacks/callbacks.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||
|
|
@ -37,7 +38,11 @@ void main() async {
|
|||
globalApplicationSupportDirectory =
|
||||
(await getApplicationSupportDirectory()).path;
|
||||
|
||||
await bridge.initializeTwonly(
|
||||
initLogger();
|
||||
|
||||
await initFlutterCallbacksForRust();
|
||||
|
||||
await bridge.initializeTwonlyFlutter(
|
||||
config: bridge.TwonlyConfig(
|
||||
databasePath: '$globalApplicationSupportDirectory/twonly.sqlite',
|
||||
dataDirectory: globalApplicationSupportDirectory,
|
||||
|
|
@ -68,8 +73,6 @@ void main() async {
|
|||
await deleteLocalUserData();
|
||||
}
|
||||
|
||||
initLogger();
|
||||
|
||||
final settingsController = SettingsChangeProvider();
|
||||
|
||||
await settingsController.loadSettings();
|
||||
|
|
|
|||
28
lib/src/callbacks/callbacks.dart
Normal file
28
lib/src/callbacks/callbacks.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:twonly/core/bridge/callbacks.dart';
|
||||
import 'package:twonly/src/callbacks/logging.callbacks.dart';
|
||||
import 'package:twonly/src/callbacks/user_discovery.callbacks.dart';
|
||||
|
||||
Future<void> initFlutterCallbacksForRust() async {
|
||||
await initFlutterCallbacks(
|
||||
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
|
||||
userDiscoverySetShares: UserDiscoveryCallbacks.setShares,
|
||||
userDiscoveryGetShareForContact:
|
||||
UserDiscoveryCallbacks.userDiscoveryGetShareForContact,
|
||||
userDiscoveryPushOwnPromotion: UserDiscoveryCallbacks.pushOwnPromotion,
|
||||
userDiscoveryPushNewUserRelation:
|
||||
UserDiscoveryCallbacks.pushNewUserRelation,
|
||||
userDiscoveryGetOwnPromotionsAfterVersion:
|
||||
UserDiscoveryCallbacks.getOwnPromotionsAfterVersion,
|
||||
userDiscoveryStoreOtherPromotion:
|
||||
UserDiscoveryCallbacks.storeOtherPromotion,
|
||||
userDiscoveryGetOtherPromotionsByPublicId:
|
||||
UserDiscoveryCallbacks.getOtherPromotionsByPublicId,
|
||||
userDiscoveryGetAnnouncedUserByPublicId:
|
||||
UserDiscoveryCallbacks.getAnnouncedUserByPublicId,
|
||||
userDiscoveryGetContactVersion: UserDiscoveryCallbacks.getContactVersion,
|
||||
userDiscoverySetContactVersion: UserDiscoveryCallbacks.setContactVersion,
|
||||
userDiscoverySignData: UserDiscoveryCallbacks.signData,
|
||||
userDiscoveryVerifySignature: UserDiscoveryCallbacks.verifySignature,
|
||||
userDiscoveryVerifyStoredPubkey: UserDiscoveryCallbacks.verifyStoredPubKey,
|
||||
);
|
||||
}
|
||||
32
lib/src/callbacks/logging.callbacks.dart
Normal file
32
lib/src/callbacks/logging.callbacks.dart
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
class LoggingCallbacks {
|
||||
static Future<RustStreamSink<String>> getStreamSink() async {
|
||||
final dartLogSink = RustStreamSink<String>();
|
||||
|
||||
Timer.periodic(const Duration(milliseconds: 100), (timer) {
|
||||
try {
|
||||
dartLogSink.stream.listen(
|
||||
(log) {
|
||||
if (log.contains('INFO ')) {
|
||||
Log.info(log.split('INFO ')[1]);
|
||||
} else if (log.contains('DEBUG ')) {
|
||||
Log.info(log.split('DEBUG ')[1]);
|
||||
} else if (kDebugMode) {
|
||||
print(log);
|
||||
}
|
||||
},
|
||||
onDone: () => Log.error('Log stream closed'),
|
||||
);
|
||||
timer.cancel();
|
||||
} catch (e) {
|
||||
// stream not yet initialized
|
||||
}
|
||||
});
|
||||
|
||||
return dartLogSink;
|
||||
}
|
||||
}
|
||||
294
lib/src/callbacks/user_discovery.callbacks.dart
Normal file
294
lib/src/callbacks/user_discovery.callbacks.dart
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'
|
||||
show Curve, IdentityKey;
|
||||
// ignore: implementation_imports
|
||||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
||||
import 'package:twonly/core/bridge.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class UserDiscoveryCallbacks {
|
||||
static Future<Uint8List?> signData(
|
||||
Uint8List inputData,
|
||||
) async {
|
||||
var privKey = (await getSignalIdentityKeyPair())?.getPrivateKey();
|
||||
if (privKey == null) return null;
|
||||
final random = getRandomUint8List(32);
|
||||
final signature = sign(
|
||||
privKey.serialize(),
|
||||
inputData,
|
||||
random,
|
||||
);
|
||||
privKey = null;
|
||||
return signature;
|
||||
}
|
||||
|
||||
static Future<bool> verifySignature(
|
||||
Uint8List inputData,
|
||||
Uint8List pubKey,
|
||||
Uint8List signature,
|
||||
) async {
|
||||
return Curve.verifySignature(
|
||||
IdentityKey.fromBytes(pubKey, 0).publicKey,
|
||||
inputData,
|
||||
signature,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> verifyStoredPubKey(
|
||||
int contactId,
|
||||
Uint8List pubKey,
|
||||
) async {
|
||||
final storedPublicKey = await getPublicKeyFromContact(contactId);
|
||||
if (storedPublicKey != null) {
|
||||
return storedPublicKey == pubKey;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> setShares(List<Uint8List> shares) async {
|
||||
try {
|
||||
// First remove all old shares then insert all the new shares
|
||||
await twonlyDB.delete(twonlyDB.userDiscoveryShares).go();
|
||||
await twonlyDB.batch((b) {
|
||||
b.insertAll(
|
||||
twonlyDB.userDiscoveryShares,
|
||||
shares
|
||||
.map((s) => UserDiscoverySharesCompanion(share: Value(s)))
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Uint8List?> userDiscoveryGetShareForContact(
|
||||
int contactId,
|
||||
) async {
|
||||
return twonlyDB.transaction(() async {
|
||||
// 1. Check if this contact already has a share assigned
|
||||
final existing =
|
||||
await (twonlyDB.select(twonlyDB.userDiscoveryShares)
|
||||
..where((tbl) => tbl.contactId.equals(contactId))
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
|
||||
if (existing != null) {
|
||||
return existing.share;
|
||||
}
|
||||
|
||||
// 2. No share found. Find an available one (where contactId is null)
|
||||
final available =
|
||||
await (twonlyDB.select(twonlyDB.userDiscoveryShares)
|
||||
..where((tbl) => tbl.contactId.isNull())
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
|
||||
if (available != null) {
|
||||
// 3. Assign the contactId to this available share
|
||||
await (twonlyDB.update(
|
||||
twonlyDB.userDiscoveryShares,
|
||||
)..where((tbl) => tbl.shareId.equals(available.shareId))).write(
|
||||
UserDiscoverySharesCompanion(
|
||||
contactId: Value(contactId),
|
||||
),
|
||||
);
|
||||
|
||||
return available.share;
|
||||
}
|
||||
|
||||
return null; // 4. No existing or available shares found
|
||||
});
|
||||
}
|
||||
|
||||
static Future<bool> pushOwnPromotion(
|
||||
int contactId,
|
||||
int version, // Maps to versionId or logic control
|
||||
Uint8List promotion,
|
||||
) async {
|
||||
try {
|
||||
await twonlyDB
|
||||
.into(twonlyDB.userDiscoveryOwnPromotions)
|
||||
.insert(
|
||||
UserDiscoveryOwnPromotionsCompanion.insert(
|
||||
contactId: contactId,
|
||||
promotion: promotion,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<Uint8List>> getOwnPromotionsAfterVersion(
|
||||
int version,
|
||||
) async {
|
||||
final query = twonlyDB.select(twonlyDB.userDiscoveryOwnPromotions)
|
||||
..where((tbl) => tbl.versionId.isBiggerThanValue(version));
|
||||
|
||||
final rows = await query.get();
|
||||
return rows.map((r) => r.promotion).toList();
|
||||
}
|
||||
|
||||
static Future<bool> storeOtherPromotion(
|
||||
OtherPromotion promotion,
|
||||
) async {
|
||||
try {
|
||||
await twonlyDB
|
||||
.into(twonlyDB.userDiscoveryOtherPromotions)
|
||||
.insertOnConflictUpdate(
|
||||
UserDiscoveryOtherPromotionsCompanion(
|
||||
promotionId: Value(promotion.promotionId),
|
||||
publicId: Value(promotion.publicId),
|
||||
fromContactId: Value(promotion.fromContactId),
|
||||
threshold: Value(promotion.threshold),
|
||||
announcementShare: Value(promotion.announcementShare),
|
||||
publicKeyVerifiedTimestamp: Value(
|
||||
promotion.publicKeyVerifiedTimestamp == null
|
||||
? null
|
||||
: DateTime.fromMillisecondsSinceEpoch(
|
||||
promotion.publicKeyVerifiedTimestamp!,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<OtherPromotion>> getOtherPromotionsByPublicId(
|
||||
int publicId,
|
||||
) async {
|
||||
final rows = await (twonlyDB.select(
|
||||
twonlyDB.userDiscoveryOtherPromotions,
|
||||
)..where((tbl) => tbl.publicId.equals(publicId))).get();
|
||||
|
||||
return rows
|
||||
.map(
|
||||
(row) => OtherPromotion(
|
||||
promotionId: row.promotionId,
|
||||
publicId: row.publicId,
|
||||
fromContactId: row.fromContactId,
|
||||
threshold: row.threshold,
|
||||
announcementShare: row.announcementShare,
|
||||
publicKeyVerifiedTimestamp:
|
||||
row.publicKeyVerifiedTimestamp?.millisecondsSinceEpoch,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<AnnouncedUser?> getAnnouncedUserByPublicId(
|
||||
int publicId,
|
||||
) async {
|
||||
final row = await (twonlyDB.select(
|
||||
twonlyDB.userDiscoveryAnnouncedUsers,
|
||||
)..where((tbl) => tbl.publicId.equals(publicId))).getSingleOrNull();
|
||||
if (row == null) return null;
|
||||
return AnnouncedUser(
|
||||
userId: row.announcedUserId,
|
||||
publicKey: row.announcedPublicKey,
|
||||
publicId: row.publicId,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> pushNewUserRelation(
|
||||
int fromContactId,
|
||||
AnnouncedUser announcedUser,
|
||||
int? publicKeyVerifiedTimestamp,
|
||||
) async {
|
||||
try {
|
||||
await twonlyDB.transaction(() async {
|
||||
// 1. Ensure the user exists in the AnnouncedUsers table
|
||||
await twonlyDB
|
||||
.into(twonlyDB.userDiscoveryAnnouncedUsers)
|
||||
.insertOnConflictUpdate(
|
||||
UserDiscoveryAnnouncedUser(
|
||||
announcedUserId: announcedUser.userId,
|
||||
announcedPublicKey: announcedUser.publicKey,
|
||||
publicId: announcedUser.publicId,
|
||||
),
|
||||
);
|
||||
|
||||
// 2. Insert or update the relation
|
||||
await twonlyDB
|
||||
.into(twonlyDB.userDiscoveryUserRelations)
|
||||
.insertOnConflictUpdate(
|
||||
UserDiscoveryUserRelationsCompanion.insert(
|
||||
announcedUserId: announcedUser.userId,
|
||||
fromContactId: fromContactId,
|
||||
publicKeyVerifiedTimestamp: Value(
|
||||
publicKeyVerifiedTimestamp != null
|
||||
? DateTime.fromMillisecondsSinceEpoch(
|
||||
publicKeyVerifiedTimestamp,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// static Future<Map<AnnouncedUser, List<(int, DateTime?)>>>
|
||||
// getAllAnnouncedUsers() async {
|
||||
// final query = twonlyDB.select(twonlyDB.userDiscoveryAnnouncedUsers).join([
|
||||
// innerJoin(
|
||||
// twonlyDB.userDiscoveryUserRelations,
|
||||
// twonlyDB.userDiscoveryUserRelations.announcedUserId.equalsExp(
|
||||
// twonlyDB.userDiscoveryAnnouncedUsers.announcedUserId,
|
||||
// ),
|
||||
// ),
|
||||
// ]);
|
||||
|
||||
// final results = await query.get();
|
||||
// final map = <UserDiscoveryAnnouncedUser, List<(int, DateTime?)>>{};
|
||||
|
||||
// for (final row in results) {
|
||||
// final user = row.readTable(twonlyDB.userDiscoveryAnnouncedUsers);
|
||||
// final relation = row.readTable(twonlyDB.userDiscoveryUserRelations);
|
||||
|
||||
// map.putIfAbsent(user, () => []).add(
|
||||
// (relation.fromContactId, relation.publicKeyVerifiedTimestamp),
|
||||
// );
|
||||
// }
|
||||
|
||||
// return map;
|
||||
// }
|
||||
|
||||
static Future<Uint8List?> getContactVersion(int contactId) async {
|
||||
final row = await (twonlyDB.select(
|
||||
twonlyDB.contacts,
|
||||
)..where((tbl) => tbl.userId.equals(contactId))).getSingleOrNull();
|
||||
return row?.userDiscoveryVersion;
|
||||
}
|
||||
|
||||
static Future<bool> setContactVersion(int contactId, Uint8List update) async {
|
||||
try {
|
||||
await (twonlyDB.update(twonlyDB.contacts)
|
||||
..where((tbl) => tbl.userId.equals(contactId)))
|
||||
.write(ContactsCompanion(userDiscoveryVersion: Value(update)));
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,8 @@ class Routes {
|
|||
static const String settingsPrivacy = '/settings/privacy';
|
||||
static const String settingsPrivacyBlockUsers =
|
||||
'/settings/privacy/block_users';
|
||||
static const String settingsPrivacyUserDiscovery =
|
||||
'/settings/privacy/user_discovery';
|
||||
static const String settingsNotification = '/settings/notification';
|
||||
static const String settingsStorage = '/settings/storage_data';
|
||||
static const String settingsStorageImport = '/settings/storage_data/import';
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ class UserData {
|
|||
@JsonKey(defaultValue: false)
|
||||
bool isUserDiscoveryEnabled = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
int minimumRequiredImagesExchanged = 4;
|
||||
|
||||
// -- Custom DATA --
|
||||
|
||||
@JsonKey(defaultValue: 100_000)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ import 'package:twonly/src/views/settings/help/faq/verifybadge.dart';
|
|||
import 'package:twonly/src/views/settings/help/help.view.dart';
|
||||
import 'package:twonly/src/views/settings/notification.view.dart';
|
||||
import 'package:twonly/src/views/settings/privacy.view.dart';
|
||||
import 'package:twonly/src/views/settings/privacy_view_block.view.dart';
|
||||
import 'package:twonly/src/views/settings/privacy/block_users.view.dart';
|
||||
import 'package:twonly/src/views/settings/privacy/user_discovery.view.dart';
|
||||
import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart';
|
||||
import 'package:twonly/src/views/settings/profile/profile.view.dart';
|
||||
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
||||
|
|
@ -200,7 +201,11 @@ final routerProvider = GoRouter(
|
|||
routes: [
|
||||
GoRoute(
|
||||
path: 'block_users',
|
||||
builder: (context, state) => const PrivacyViewBlockUsersView(),
|
||||
builder: (context, state) => const BlockUsersView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'user_discovery',
|
||||
builder: (context, state) => const UserDiscoverySettingsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
26
lib/src/services/user_discovery.service.dart
Normal file
26
lib/src/services/user_discovery.service.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:twonly/core/bridge/wrapper/user_discovery.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/qr.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
Future<void> initializeOrUpdateUserDiscovery({
|
||||
required int threshold,
|
||||
required int minimumRequiredImagesExchanged,
|
||||
}) async {
|
||||
try {
|
||||
await FlutterUserDiscovery.initializeOrUpdate(
|
||||
threshold: threshold,
|
||||
userId: gUser.userId,
|
||||
publicKey: await getUserPublicKey(),
|
||||
);
|
||||
await updateUserdata((u) {
|
||||
u
|
||||
..isUserDiscoveryEnabled = true
|
||||
..minimumRequiredImagesExchanged = minimumRequiredImagesExchanged;
|
||||
return u;
|
||||
});
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,32 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
final primaryColor = const Color(0xFF57CC99);
|
||||
|
||||
final ThemeData lightTheme = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFF57CC99),
|
||||
seedColor: primaryColor,
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
);
|
||||
|
||||
final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: Colors.black87,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
// Adjusting the border radius (default is usually 20+)
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8), // Lower number = sharper corners
|
||||
),
|
||||
);
|
||||
|
||||
final ButtonStyle secondaryGreyButtonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: Colors.grey[200],
|
||||
foregroundColor: Colors.black87,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
// Adjusting the border radius (default is usually 20+)
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8), // Lower number = sharper corners
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -404,3 +404,50 @@ Future<List<int>> sha256File(File file) async {
|
|||
converter.close();
|
||||
return sha256Sink.events.single.bytes;
|
||||
}
|
||||
|
||||
List<TextSpan> formattedText(String input) {
|
||||
// Pattern to find text between asterisks
|
||||
final regex = RegExp(r'\*(.*?)\*');
|
||||
final List<TextSpan> spans = [];
|
||||
|
||||
// Track the current position in the string
|
||||
int lastMatchEnd = 0;
|
||||
|
||||
for (final match in regex.allMatches(input)) {
|
||||
// Add text before the match (Normal style)
|
||||
if (match.start > lastMatchEnd) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: input.substring(lastMatchEnd, match.start),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add the matched text (Bold style)
|
||||
// match.group(1) is the text without the asterisks
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: match.group(1),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
lastMatchEnd = match.end;
|
||||
}
|
||||
|
||||
// Add any remaining text after the last match
|
||||
if (lastMatchEnd < input.length) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: input.substring(lastMatchEnd),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return spans;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,13 @@ class _PrivacyViewState extends State<PrivacyView> {
|
|||
setState(() {});
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Freunde finden'),
|
||||
onTap: () async {
|
||||
await context.push(Routes.settingsPrivacyUserDiscovery);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: Text(context.lang.settingsTypingIndication),
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
||||
|
||||
class PrivacyViewBlockUsersView extends StatefulWidget {
|
||||
const PrivacyViewBlockUsersView({super.key});
|
||||
class BlockUsersView extends StatefulWidget {
|
||||
const BlockUsersView({super.key});
|
||||
|
||||
@override
|
||||
State<PrivacyViewBlockUsersView> createState() => _PrivacyViewBlockUsers();
|
||||
State<BlockUsersView> createState() => _PrivacyViewBlockUsers();
|
||||
}
|
||||
|
||||
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsersView> {
|
||||
class _PrivacyViewBlockUsers extends State<BlockUsersView> {
|
||||
late Stream<List<Contact>> allUsers;
|
||||
List<Contact> filteredUsers = [];
|
||||
String filter = '';
|
||||
26
lib/src/views/settings/privacy/user_discovery.view.dart
Normal file
26
lib/src/views/settings/privacy/user_discovery.view.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/views/settings/privacy/user_discovery/user_discovery_disabled.component.dart';
|
||||
import 'package:twonly/src/views/settings/privacy/user_discovery/user_discovery_enabled.component.dart';
|
||||
|
||||
class UserDiscoverySettingsView extends StatefulWidget {
|
||||
const UserDiscoverySettingsView({super.key});
|
||||
|
||||
@override
|
||||
State<UserDiscoverySettingsView> createState() =>
|
||||
_UserDiscoverySettingsViewState();
|
||||
}
|
||||
|
||||
class _UserDiscoverySettingsViewState extends State<UserDiscoverySettingsView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Freunde finden'),
|
||||
),
|
||||
body: gUser.isUserDiscoveryEnabled
|
||||
? UserDiscoveryEnabledComponent(onUpdate: () => setState(() {}))
|
||||
: UserDiscoveryDisabledComponent(onUpdate: () => setState(() {})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||
import 'package:twonly/src/themes/light.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class UserDiscoveryDisabledComponent extends StatefulWidget {
|
||||
const UserDiscoveryDisabledComponent({required this.onUpdate, super.key});
|
||||
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
@override
|
||||
State<UserDiscoveryDisabledComponent> createState() =>
|
||||
_UserDiscoveryDisabledComponentState();
|
||||
}
|
||||
|
||||
class _UserDiscoveryDisabledComponentState
|
||||
extends State<UserDiscoveryDisabledComponent> {
|
||||
Future<void> initializeUserDiscoveryWithDefaultSettings() async {
|
||||
await initializeOrUpdateUserDiscovery(
|
||||
threshold: 2,
|
||||
minimumRequiredImagesExchanged: 4,
|
||||
);
|
||||
widget.onUpdate();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 45),
|
||||
const Text(
|
||||
'twonly verzichten auf Telefonnummern, daher schlagen wir dir Freunde stattdessen über gemeinsame Kontakte vor – sicher und privat.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: formattedText(
|
||||
'Deine Freundesliste ist für *Fremde komplett unsichtbar*. Nur deine Freunde können Teile davon sehen – und zwar nur die Personen, mit denen sie selbst *gemeinsame Freunde* haben.',
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 35),
|
||||
const Text(
|
||||
'Du hast die Kontrolle',
|
||||
style: TextStyle(fontSize: 17),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
'Entscheide selbst, wer deine Freunde sehen darf. Du kannst deine Meinung jederzeit ändern oder bestimmte Personen verstecken.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: 50),
|
||||
|
||||
FilledButton(
|
||||
onPressed: initializeUserDiscoveryWithDefaultSettings,
|
||||
style: primaryColorButtonStyle.merge(
|
||||
FilledButton.styleFrom(
|
||||
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 24),
|
||||
),
|
||||
),
|
||||
child: const Text('Mit Standardeinstellungen aktivieren'),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: FilledButton(
|
||||
onPressed: () {},
|
||||
style: secondaryGreyButtonStyle,
|
||||
child: const Text('Einstellungen anpassen'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: FilledButton(
|
||||
onPressed: () {},
|
||||
style: secondaryGreyButtonStyle,
|
||||
child: const Text('Mehr erfahren'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class UserDiscoveryEnabledComponent extends StatefulWidget {
|
||||
const UserDiscoveryEnabledComponent({required this.onUpdate, super.key});
|
||||
|
||||
final VoidCallback onUpdate;
|
||||
|
||||
@override
|
||||
State<UserDiscoveryEnabledComponent> createState() =>
|
||||
_UserDiscoveryEnabledComponentState();
|
||||
}
|
||||
|
||||
class _UserDiscoveryEnabledComponentState
|
||||
extends State<UserDiscoveryEnabledComponent> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: ListView(
|
||||
children: [
|
||||
const ExpansionTile(
|
||||
shape: RoundedRectangleBorder(),
|
||||
collapsedShape: RoundedRectangleBorder(),
|
||||
tilePadding: EdgeInsets.symmetric(horizontal: 17),
|
||||
title: Text('Freunde die du teilst'),
|
||||
subtitle: Text(
|
||||
'Du teilst nur Freunde, die diese Funktion ebenfalls aktiviert haben und die den von dir festgelegten Schwellenwert erreicht haben.',
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
children: [],
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Einstellungen ändern'),
|
||||
// onTap: () {},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: Text('Deaktivieren'),
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
38
pubspec.lock
38
pubspec.lock
|
|
@ -412,26 +412,26 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: drift
|
||||
sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e"
|
||||
sha256: "055c249d1f91be5a47fe447f88afc24c4ca6f4cd6c5ed66767b4797d48acc2e5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.31.0"
|
||||
version: "2.32.1"
|
||||
drift_dev:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: drift_dev
|
||||
sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587"
|
||||
sha256: "88a9de3af8571518148a6d8a513b57779fd1e60a026d3ab8a481a878fba01d91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.31.0"
|
||||
version: "2.32.1"
|
||||
drift_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: drift_flutter
|
||||
sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7
|
||||
sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
version: "0.3.0"
|
||||
ed25519_edwards:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
|
|
@ -1809,30 +1809,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqlite3:
|
||||
dependency: "direct main"
|
||||
sqlcipher_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
|
||||
name: sqlcipher_flutter_libs
|
||||
sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.4"
|
||||
version: "0.7.0+eol"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
|
||||
sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.42"
|
||||
version: "0.6.0+eol"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlparser
|
||||
sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19"
|
||||
sha256: ab2b467425f1d4f3acfa5fd11a08226f7d6c26ff102c06be1807e1dff34e050b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.43.1"
|
||||
version: "0.44.3"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -58,9 +58,8 @@ dependencies:
|
|||
json_annotation: ^4.9.0 # google.dev
|
||||
protobuf: ^4.0.0 # google.dev
|
||||
scrollable_positioned_list: ^0.3.8 # google.dev
|
||||
drift: ^2.25.1
|
||||
drift_flutter: ^0.2.4
|
||||
sqlite3: ^2.9.4
|
||||
drift: ^2.32.0
|
||||
drift_flutter: ^0.3.0
|
||||
|
||||
|
||||
# Flutter Favorite
|
||||
|
|
|
|||
166
rust/Cargo.lock
generated
166
rust/Cargo.lock
generated
|
|
@ -305,6 +305,15 @@ version = "2.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
|
|
@ -392,6 +401,15 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
|
@ -1071,9 +1089,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.35.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
|
@ -1107,6 +1125,15 @@ version = "0.4.29"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
|
@ -1149,6 +1176,15 @@ version = "0.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.6"
|
||||
|
|
@ -1165,6 +1201,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
|
|
@ -1260,6 +1302,12 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
|
|
@ -1340,6 +1388,12 @@ dependencies = [
|
|||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
|
|
@ -1582,7 +1636,7 @@ name = "rust_lib_twonly"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flutter_rust_bridge",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"pretty_env_logger",
|
||||
"prost-build",
|
||||
"protocols",
|
||||
|
|
@ -1592,6 +1646,8 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1725,6 +1781,15 @@ dependencies = [
|
|||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
@ -2009,6 +2074,12 @@ version = "2.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "symlink"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
|
|
@ -2073,6 +2144,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpool"
|
||||
version = "1.8.1"
|
||||
|
|
@ -2082,6 +2162,37 @@ dependencies = [
|
|||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
|
|
@ -2158,6 +2269,19 @@ dependencies = [
|
|||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"symlink",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
|
|
@ -2176,6 +2300,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2235,6 +2389,12 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,35 @@
|
|||
[workspace]
|
||||
members = ["protocols", "core"]
|
||||
resolver = "3"
|
||||
[package]
|
||||
name = "rust_lib_twonly"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
flutter_rust_bridge = "=2.12.0"
|
||||
thiserror = "2.0.18"
|
||||
sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
"migrate",
|
||||
"macros",
|
||||
"chrono",
|
||||
"derive",
|
||||
"json",
|
||||
] }
|
||||
tokio = { version = "1.44", features = ["full"] }
|
||||
tracing = "0.1.44"
|
||||
rand = "0.10.1"
|
||||
protocols = { path = "../rust_dependencies/protocols" }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tracing-appender = "0.2.5"
|
||||
paste = "1.0.15"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_env_logger = "0.5.0"
|
||||
tempfile = "3.27.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.14.1"
|
||||
|
|
|
|||
47
rust/src/bridge/callbacks.rs
Normal file
47
rust/src/bridge/callbacks.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
pub(crate) mod log;
|
||||
mod macros;
|
||||
pub(crate) mod user_discovery;
|
||||
|
||||
use flutter_rust_bridge::DartFnFuture;
|
||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion};
|
||||
|
||||
use super::error::Result;
|
||||
use crate::{callback_generator, frb_generated::StreamSink};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use crate::bridge::error::TwonlyError;
|
||||
|
||||
static FLUTTER_CALLBACKS: OnceLock<FlutterCallbacks> = OnceLock::new();
|
||||
|
||||
// This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks
|
||||
callback_generator! {
|
||||
FlutterCallbacks {
|
||||
Logging logging {
|
||||
get_stream_sink: () => StreamSink<String>
|
||||
},
|
||||
UserDiscoveryCallbacks user_discovery {
|
||||
// UserDiscoveryUtils
|
||||
sign_data: (Vec<u8>) => Option<Vec<u8>>,
|
||||
verify_signature: (Vec<u8>, Vec<u8>, Vec<u8>) => bool,
|
||||
verify_stored_pubkey: (i64, Vec<u8>) => bool,
|
||||
|
||||
// UserDiscoveryStore
|
||||
set_shares: (Vec<Vec<u8>>) => bool,
|
||||
get_share_for_contact: (i64) => Option<Vec<u8>>,
|
||||
push_own_promotion: (i64, i64, Vec<u8>) => bool,
|
||||
get_own_promotions_after_version: (i64) => Option<Vec<Vec<u8>>>,
|
||||
store_other_promotion: (OtherPromotion) => bool,
|
||||
get_other_promotions_by_public_id: (i64) => Option<Vec<OtherPromotion>>,
|
||||
get_announced_user_by_public_id: (i64) => Option<AnnouncedUser>,
|
||||
get_contact_version: (i64) => Option<Vec<u8>>,
|
||||
set_contact_version: (i64, Vec<u8>) => bool,
|
||||
push_new_user_relation: (i64, AnnouncedUser, Option<i64>) => bool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_callbacks() -> Result<&'static FlutterCallbacks> {
|
||||
FLUTTER_CALLBACKS
|
||||
.get()
|
||||
.ok_or(TwonlyError::MissingCallbackInitialization)
|
||||
}
|
||||
28
rust/src/bridge/callbacks/log.rs
Normal file
28
rust/src/bridge/callbacks/log.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use crate::frb_generated::StreamSink;
|
||||
use tracing_subscriber::fmt::MakeWriter;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DartWriter {
|
||||
pub(crate) sink: StreamSink<String>,
|
||||
}
|
||||
|
||||
impl std::io::Write for DartWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if let Ok(msg) = std::str::from_utf8(buf) {
|
||||
let _ = self.sink.add(msg.trim_end().to_string());
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MakeWriter<'a> for DartWriter {
|
||||
type Writer = DartWriter;
|
||||
|
||||
fn make_writer(&'a self) -> Self::Writer {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
57
rust/src/bridge/callbacks/macros.rs
Normal file
57
rust/src/bridge/callbacks/macros.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#[macro_export]
|
||||
macro_rules! callback_generator {
|
||||
(
|
||||
$struct_name:ident {
|
||||
$(
|
||||
$sub_struct_name:ident $sub_struct_field:ident {
|
||||
$(
|
||||
$fn_name:ident : ($($input:ty),*) => $output:ty
|
||||
),* $(,)?
|
||||
}
|
||||
),* $(,)?
|
||||
}
|
||||
) => {
|
||||
// 1. Generate the Nested Sub-Structs
|
||||
$(
|
||||
pub(crate) struct $sub_struct_name {
|
||||
$(
|
||||
pub(crate) $fn_name: Arc<dyn Fn($($input),*) -> DartFnFuture<$output> + Send + Sync + 'static>,
|
||||
)*
|
||||
}
|
||||
)*
|
||||
|
||||
// 2. Generate the Main Container Struct
|
||||
pub(crate) struct $struct_name {
|
||||
$(
|
||||
pub(crate) $sub_struct_field: $sub_struct_name,
|
||||
)*
|
||||
}
|
||||
|
||||
// 3. Generate the Automated Init Function
|
||||
paste::paste! {
|
||||
pub async fn init_flutter_callbacks(
|
||||
$(
|
||||
$(
|
||||
// Parameters: sub-struct_field + _ + fn_name
|
||||
[<$sub_struct_field _ $fn_name>]: impl Fn($($input),*) -> DartFnFuture<$output> + Send + Sync + 'static,
|
||||
)*
|
||||
)*
|
||||
) {
|
||||
let callbacks = $struct_name {
|
||||
$(
|
||||
$sub_struct_field: $sub_struct_name {
|
||||
$(
|
||||
$fn_name: Arc::new([<$sub_struct_field _ $fn_name>]),
|
||||
)*
|
||||
},
|
||||
)*
|
||||
};
|
||||
|
||||
// Use the static global strictly named FLUTTER_CALLBACKS
|
||||
FLUTTER_CALLBACKS.set(callbacks).unwrap_or_else(|_| {
|
||||
println!("Callbacks were already initialized!");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
169
rust/src/bridge/callbacks/user_discovery.rs
Normal file
169
rust/src/bridge/callbacks/user_discovery.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
use crate::bridge::callbacks::get_callbacks;
|
||||
use crate::bridge::error::TwonlyError;
|
||||
use crate::bridge::get_twonly_flutter;
|
||||
use protocols::user_discovery::error::{Result, UserDiscoveryError};
|
||||
use protocols::user_discovery::traits::UserDiscoveryUtils;
|
||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct UserDiscoveryStoreFlutter {}
|
||||
pub(crate) struct UserDiscoveryUtilsFlutter {}
|
||||
|
||||
impl UserDiscoveryUtils for UserDiscoveryUtilsFlutter {
|
||||
async fn sign_data(&self, input_data: &[u8]) -> Result<Vec<u8>> {
|
||||
match (get_callbacks()?.user_discovery.sign_data)(input_data.to_vec()).await {
|
||||
Some(signature) => Ok(signature),
|
||||
None => Err(TwonlyError::DartError)?,
|
||||
}
|
||||
}
|
||||
|
||||
async fn verify_signature(
|
||||
&self,
|
||||
input_data: &[u8],
|
||||
pubkey: &[u8],
|
||||
signature: &[u8],
|
||||
) -> Result<bool> {
|
||||
Ok((get_callbacks()?.user_discovery.verify_signature)(
|
||||
input_data.to_vec(),
|
||||
pubkey.to_vec(),
|
||||
signature.to_vec(),
|
||||
)
|
||||
.await)
|
||||
}
|
||||
|
||||
async fn verify_stored_pubkey(&self, from_contact_id: i64, pubkey: &[u8]) -> Result<bool> {
|
||||
Ok(
|
||||
(get_callbacks()?.user_discovery.verify_stored_pubkey)(
|
||||
from_contact_id,
|
||||
pubkey.to_vec(),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
|
||||
async fn get_config(&self) -> Result<String> {
|
||||
let ws = get_twonly_flutter().unwrap();
|
||||
let config_path =
|
||||
PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json");
|
||||
|
||||
if !config_path.is_file() {
|
||||
return Err(UserDiscoveryError::NotInitialized);
|
||||
}
|
||||
|
||||
tracing::debug!("Loading Config from {}", config_path.display());
|
||||
Ok(std::fs::read_to_string(&config_path)?)
|
||||
}
|
||||
|
||||
async fn update_config(&self, update: String) -> Result<()> {
|
||||
tracing::debug!("Updating configuration file.");
|
||||
let ws = get_twonly_flutter().unwrap();
|
||||
let config_path =
|
||||
PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json");
|
||||
std::fs::write(config_path, &update)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()> {
|
||||
(get_callbacks()?.user_discovery.set_shares)(shares).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_share_for_contact(&self, contact_id: i64) -> Result<Vec<u8>> {
|
||||
match (get_callbacks()?.user_discovery.get_share_for_contact)(contact_id).await {
|
||||
Some(share) => Ok(share),
|
||||
None => Err(UserDiscoveryError::NoSharesLeft),
|
||||
}
|
||||
}
|
||||
|
||||
async fn push_own_promotion(
|
||||
&self,
|
||||
contact_id: i64,
|
||||
version: u32,
|
||||
promotion: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
(get_callbacks()?.user_discovery.push_own_promotion)(contact_id, version as i64, promotion)
|
||||
.await
|
||||
.then_some(())
|
||||
.ok_or(TwonlyError::DartError.into())
|
||||
}
|
||||
|
||||
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
||||
match (get_callbacks()?
|
||||
.user_discovery
|
||||
.get_own_promotions_after_version)(version as i64)
|
||||
.await
|
||||
{
|
||||
Some(share) => Ok(share),
|
||||
None => Err(TwonlyError::DartError)?,
|
||||
}
|
||||
}
|
||||
|
||||
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
||||
(get_callbacks()?.user_discovery.store_other_promotion)(promotion)
|
||||
.await
|
||||
.then_some(())
|
||||
.ok_or(TwonlyError::DartError.into())
|
||||
}
|
||||
|
||||
async fn get_other_promotions_by_public_id(
|
||||
&self,
|
||||
public_id: i64,
|
||||
) -> Result<Vec<OtherPromotion>> {
|
||||
match (get_callbacks()?
|
||||
.user_discovery
|
||||
.get_other_promotions_by_public_id)(public_id)
|
||||
.await
|
||||
{
|
||||
Some(promotions) => Ok(promotions),
|
||||
None => Err(TwonlyError::DartError)?,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_announced_user_by_public_id(
|
||||
&self,
|
||||
public_id: i64,
|
||||
) -> Result<Option<AnnouncedUser>> {
|
||||
Ok((get_callbacks()?
|
||||
.user_discovery
|
||||
.get_announced_user_by_public_id)(public_id)
|
||||
.await)
|
||||
}
|
||||
|
||||
async fn push_new_user_relation(
|
||||
&self,
|
||||
from_contact_id: i64,
|
||||
announced_user: AnnouncedUser,
|
||||
public_key_verified_timestamp: Option<i64>,
|
||||
) -> Result<()> {
|
||||
(get_callbacks()?.user_discovery.push_new_user_relation)(
|
||||
from_contact_id,
|
||||
announced_user,
|
||||
public_key_verified_timestamp,
|
||||
)
|
||||
.await
|
||||
.then_some(())
|
||||
.ok_or(TwonlyError::DartError.into())
|
||||
}
|
||||
|
||||
async fn get_all_announced_users(
|
||||
&self,
|
||||
) -> Result<HashMap<AnnouncedUser, Vec<(i64, Option<i64>)>>> {
|
||||
// This is never called from the RUST code.
|
||||
Err(TwonlyError::DartError)?
|
||||
}
|
||||
|
||||
async fn get_contact_version(&self, contact_id: i64) -> Result<Option<Vec<u8>>> {
|
||||
Ok((get_callbacks()?.user_discovery.get_contact_version)(contact_id).await)
|
||||
}
|
||||
|
||||
async fn set_contact_version(&self, contact_id: i64, update: Vec<u8>) -> Result<()> {
|
||||
(get_callbacks()?.user_discovery.set_contact_version)(contact_id, update)
|
||||
.await
|
||||
.then_some(())
|
||||
.ok_or(TwonlyError::DartError.into())
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,15 @@ pub type Result<T> = core::result::Result<T, TwonlyError>;
|
|||
pub enum TwonlyError {
|
||||
#[error("global twonly is not initialized")]
|
||||
Initialization,
|
||||
#[error("init_flutter_callbacks was not called")]
|
||||
MissingCallbackInitialization,
|
||||
#[error("Could not find the given database")]
|
||||
DatabaseNotFound,
|
||||
#[error("{0}")]
|
||||
UserDiscoveryError(#[from] UserDiscoveryError),
|
||||
#[error("Error in dart callback")]
|
||||
DartError,
|
||||
#[error("{0}")]
|
||||
SqliteError(#[from] sqlx::Error),
|
||||
}
|
||||
|
||||
|
|
|
|||
73
rust/src/bridge/log.rs
Normal file
73
rust/src/bridge/log.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use crate::bridge::callbacks::{get_callbacks, log::DartWriter};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
|
||||
use tracing_subscriber::{
|
||||
fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry,
|
||||
};
|
||||
|
||||
static TRACING_GUARDS: OnceLock<Mutex<Option<(WorkerGuard, WorkerGuard)>>> = OnceLock::new();
|
||||
static TRACING_INIT: OnceLock<()> = OnceLock::new();
|
||||
|
||||
pub(crate) async fn init_tracing(logs_dir: &std::path::Path, is_dart_available: bool) {
|
||||
let _ = std::fs::create_dir_all(logs_dir);
|
||||
|
||||
let mut dart_sink = None;
|
||||
|
||||
if is_dart_available {
|
||||
if let Ok(callbacks) = get_callbacks() {
|
||||
dart_sink = Some((callbacks.logging.get_stream_sink)().await);
|
||||
}
|
||||
}
|
||||
|
||||
TRACING_INIT.get_or_init(|| {
|
||||
let (non_blocking_stdout, _non_blocking_file) = build_writers(logs_dir);
|
||||
|
||||
let stdout_layer = Layer::new()
|
||||
.with_writer(non_blocking_stdout)
|
||||
.with_ansi(true)
|
||||
.with_target(true);
|
||||
|
||||
// let file_layer = Layer::new()
|
||||
// .with_writer(non_blocking_file)
|
||||
// .with_ansi(false)
|
||||
// .with_target(true);
|
||||
|
||||
// Replace stdout with our new DartWriter!
|
||||
|
||||
let registry = Registry::default()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("debug,refinery_core=warn,refinery=warn")),
|
||||
)
|
||||
.with(stdout_layer);
|
||||
|
||||
if let Some(sink) = dart_sink {
|
||||
let dart_writer = DartWriter { sink };
|
||||
let dart_layer = tracing_subscriber::fmt::Layer::new()
|
||||
.with_writer(dart_writer)
|
||||
.with_ansi(false)
|
||||
.with_target(true);
|
||||
let _ = registry.with(dart_layer).try_init();
|
||||
} else {
|
||||
let _ = registry.try_init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn build_writers(logs_dir: &std::path::Path) -> (NonBlocking, NonBlocking) {
|
||||
let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
|
||||
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
||||
.filename_prefix("twonly")
|
||||
.filename_suffix("log")
|
||||
.build(logs_dir)
|
||||
.expect("Failed to create file appender");
|
||||
|
||||
let (non_blocking_file, file_guard) = tracing_appender::non_blocking(file_appender);
|
||||
let (non_blocking_stdout, stdout_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||
|
||||
TRACING_GUARDS
|
||||
.set(Mutex::new(Some((file_guard, stdout_guard))))
|
||||
.ok();
|
||||
|
||||
(non_blocking_stdout, non_blocking_file)
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
static TRACING_INIT: OnceLock<()> = OnceLock::new();
|
||||
|
||||
pub(crate) fn init_tracing(logs_dir: &std::path::Path) {
|
||||
TRACING_INIT.get_or_init(|| {
|
||||
let (non_blocking_stdout, non_blocking_file) = build_writers(logs_dir);
|
||||
|
||||
let stdout_layer = Layer::new()
|
||||
.with_writer(non_blocking_stdout)
|
||||
.with_ansi(true)
|
||||
.with_target(true);
|
||||
|
||||
let file_layer = Layer::new()
|
||||
.with_writer(non_blocking_file)
|
||||
.with_ansi(false)
|
||||
.with_target(true);
|
||||
|
||||
Registry::default()
|
||||
.with(
|
||||
EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("info,refinery_core=warn,refinery=warn")),
|
||||
)
|
||||
.with(stdout_layer)
|
||||
.with(file_layer)
|
||||
.init();
|
||||
});
|
||||
}
|
||||
|
||||
fn build_writers(logs_dir: &std::path::Path) -> (NonBlocking, NonBlocking) {
|
||||
let file_appender = tracing_appender::rolling::RollingFileAppender::builder()
|
||||
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
||||
.filename_prefix("twonly")
|
||||
.filename_suffix("log")
|
||||
.build(logs_dir)
|
||||
.expect("Failed to create file appender");
|
||||
|
||||
let (non_blocking_file, file_guard) = tracing_appender::non_blocking(file_appender);
|
||||
let (non_blocking_stdout, stdout_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||
|
||||
TRACING_GUARDS
|
||||
.set(Mutex::new(Some((file_guard, stdout_guard))))
|
||||
.ok();
|
||||
|
||||
(non_blocking_stdout, non_blocking_file)
|
||||
}
|
||||
|
|
@ -1,18 +1,22 @@
|
|||
#![allow(unexpected_cfgs)]
|
||||
pub mod callbacks;
|
||||
pub mod error;
|
||||
mod user_discovery_utils;
|
||||
use crate::bridge::user_discovery_utils::UserDiscoveryUtilsFlutter;
|
||||
use crate::database::contact::Contact;
|
||||
use crate::database::Database;
|
||||
use crate::user_discovery_store::UserDiscoveryDatabaseStore;
|
||||
pub mod log;
|
||||
pub mod wrapper;
|
||||
|
||||
use crate::bridge::callbacks::user_discovery::{
|
||||
UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter,
|
||||
};
|
||||
use crate::bridge::log::init_tracing;
|
||||
use crate::utils::Shared;
|
||||
use error::Result;
|
||||
use error::TwonlyError;
|
||||
use flutter_rust_bridge::frb;
|
||||
use protocols::user_discovery::UserDiscovery;
|
||||
use std::sync::Arc;
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
pub use protocols::user_discovery::traits::AnnouncedUser;
|
||||
pub use protocols::user_discovery::traits::OtherPromotion;
|
||||
|
||||
#[frb(mirror(OtherPromotion))]
|
||||
|
|
@ -25,34 +29,56 @@ pub struct _OtherPromotion {
|
|||
pub public_key_verified_timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
#[frb(mirror(AnnouncedUser))]
|
||||
pub struct _AnnouncedUser {
|
||||
pub user_id: i64,
|
||||
pub public_key: Vec<u8>,
|
||||
pub public_id: i64,
|
||||
}
|
||||
|
||||
pub struct TwonlyConfig {
|
||||
pub database_path: String,
|
||||
pub data_directory: String,
|
||||
}
|
||||
|
||||
pub(crate) struct Twonly {
|
||||
pub(crate) struct TwonlyFlutter {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) config: TwonlyConfig,
|
||||
pub(crate) database: Arc<Database>,
|
||||
// /// Rust runs in the same process as drift, the database can only be opened in readonly mode
|
||||
// pub(crate) twonly_db_readonly: Arc<Database>,
|
||||
pub(crate) user_discovery:
|
||||
Shared<Option<UserDiscovery<UserDiscoveryDatabaseStore, UserDiscoveryUtilsFlutter>>>,
|
||||
Shared<UserDiscovery<UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter>>,
|
||||
}
|
||||
|
||||
static GLOBAL_TWONLY: OnceCell<Twonly> = OnceCell::const_new();
|
||||
static GLOBAL_TWONLY: OnceCell<TwonlyFlutter> = OnceCell::const_new();
|
||||
|
||||
pub(crate) fn get_workspace() -> Result<&'static Twonly> {
|
||||
pub(super) fn get_twonly_flutter() -> Result<&'static TwonlyFlutter> {
|
||||
GLOBAL_TWONLY.get().ok_or(TwonlyError::Initialization)
|
||||
}
|
||||
|
||||
pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
||||
tracing::debug!("Initialized twonly workspace.");
|
||||
let twonly_res: Result<&'static Twonly> = GLOBAL_TWONLY
|
||||
pub async fn initialize_twonly_flutter(config: TwonlyConfig) -> Result<()> {
|
||||
let log_dir = PathBuf::from(&config.data_directory).join("log");
|
||||
init_tracing(&log_dir, true).await;
|
||||
tracing::info!("Initialized twonly workspace.");
|
||||
let twonly_res: Result<&'static TwonlyFlutter> = GLOBAL_TWONLY
|
||||
.get_or_try_init(|| async {
|
||||
let database = Arc::new(Database::new(&config.database_path).await?);
|
||||
Ok(Twonly {
|
||||
// let database_dir = PathBuf::from(&config.database_path.clone());
|
||||
// let Some(rust_db_path) = database_dir.parent() else {
|
||||
// return Err(TwonlyError::DatabaseNotFound);
|
||||
// };
|
||||
// let rust_db_path = rust_db_path.join("rust_db.sqlite").display().to_string();
|
||||
|
||||
// let twonly_db_readonly = Arc::new(Database::new(&config.database_path, true).await?);
|
||||
// let rust_db = Arc::new(Database::new(&rust_db_path, false).await?);
|
||||
|
||||
Ok(TwonlyFlutter {
|
||||
config,
|
||||
database,
|
||||
user_discovery: Shared::default(),
|
||||
// twonly_db_readonly,
|
||||
// rust_db,
|
||||
user_discovery: Shared::new(UserDiscovery::new(
|
||||
UserDiscoveryStoreFlutter {},
|
||||
UserDiscoveryUtilsFlutter {},
|
||||
)?),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
|
@ -61,102 +87,3 @@ pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_all_contacts() -> Result<Vec<Contact>> {
|
||||
let twonly = get_workspace()?;
|
||||
Contact::get_all_contacts(twonly.database.as_ref()).await
|
||||
}
|
||||
|
||||
pub fn load_promotions() -> OtherPromotion {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||
use tempfile::{NamedTempFile, TempDir};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::{database::Database, utils::Shared};
|
||||
|
||||
use super::error::Result;
|
||||
use super::Twonly;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::{get_workspace, initialize_twonly, TwonlyConfig};
|
||||
|
||||
static TWONLY_TESTING: [OnceCell<Twonly>; 10] = [
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
OnceCell::const_new(),
|
||||
];
|
||||
|
||||
static TWONLY_TESTING_INDEX: OnceCell<Arc<Mutex<usize>>> = OnceCell::const_new();
|
||||
|
||||
pub(crate) async fn initialize_twonly_for_testing(use_global: bool) -> Result<&'static Twonly> {
|
||||
let default_twonly_database = PathBuf::from("tests/testing.db");
|
||||
|
||||
if !default_twonly_database.is_file() {
|
||||
panic!("{} not found!", default_twonly_database.display())
|
||||
}
|
||||
|
||||
let temp_file = NamedTempFile::new().unwrap().path().to_owned();
|
||||
|
||||
tracing::info!("Crated db copy: {}", temp_file.display());
|
||||
|
||||
let conn = SqlitePoolOptions::new()
|
||||
.connect_with(
|
||||
format!("sqlite://{}", default_twonly_database.display())
|
||||
.parse::<SqliteConnectOptions>()
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let path_str = temp_file.display().to_string();
|
||||
sqlx::query("VACUUM INTO $1")
|
||||
.bind(path_str)
|
||||
.execute(&conn)
|
||||
.await
|
||||
.expect("Failed to backup database");
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap().path().to_owned();
|
||||
std::fs::create_dir_all(&tmp_dir).unwrap();
|
||||
let config = TwonlyConfig {
|
||||
database_path: temp_file.display().to_string(),
|
||||
data_directory: tmp_dir.to_str().unwrap().to_string(),
|
||||
};
|
||||
|
||||
if use_global {
|
||||
initialize_twonly(config).await.unwrap();
|
||||
|
||||
get_workspace()
|
||||
} else {
|
||||
let index = TWONLY_TESTING_INDEX
|
||||
.get_or_init(|| async { Arc::default() })
|
||||
.await;
|
||||
let mut index = index.lock().await;
|
||||
let res: Result<&'static Twonly> = TWONLY_TESTING[*index]
|
||||
.get_or_try_init(|| async {
|
||||
let database = Arc::new(Database::new(&config.database_path).await?);
|
||||
Ok(Twonly {
|
||||
config,
|
||||
database,
|
||||
user_discovery: Shared::default(),
|
||||
})
|
||||
})
|
||||
.await;
|
||||
tracing::debug!("TWONLY_TESTING_INDEX: {index}");
|
||||
*index += 1;
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
use protocols::user_discovery::error::Result;
|
||||
use protocols::user_discovery::traits::UserDiscoveryUtils;
|
||||
|
||||
pub(crate) struct UserDiscoveryUtilsFlutter {}
|
||||
|
||||
impl UserDiscoveryUtils for UserDiscoveryUtilsFlutter {
|
||||
async fn sign_data(&self, input_data: &[u8]) -> Result<Vec<u8>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn verify_signature(
|
||||
&self,
|
||||
input_data: &[u8],
|
||||
pubkey: &[u8],
|
||||
signature: &[u8],
|
||||
) -> Result<bool> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn verify_stored_pubkey(
|
||||
&self,
|
||||
from_contact_id: protocols::user_discovery::UserID,
|
||||
pubkey: &[u8],
|
||||
) -> Result<bool> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
1
rust/src/bridge/wrapper/mod.rs
Normal file
1
rust/src/bridge/wrapper/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod user_discovery;
|
||||
61
rust/src/bridge/wrapper/user_discovery.rs
Normal file
61
rust/src/bridge/wrapper/user_discovery.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use crate::bridge::error::Result;
|
||||
use crate::bridge::get_twonly_flutter;
|
||||
|
||||
pub struct FlutterUserDiscovery {}
|
||||
|
||||
impl FlutterUserDiscovery {
|
||||
pub async fn initialize_or_update(
|
||||
threshold: u8,
|
||||
user_id: i64,
|
||||
public_key: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
Ok(get_twonly_flutter()?
|
||||
.user_discovery
|
||||
.get()
|
||||
.await
|
||||
.initialize_or_update(threshold, user_id, public_key)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_current_version() -> Result<Vec<u8>> {
|
||||
Ok(get_twonly_flutter()?
|
||||
.user_discovery
|
||||
.get()
|
||||
.await
|
||||
.get_current_version()
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_new_messages(
|
||||
contact_id: i64,
|
||||
received_version: &[u8],
|
||||
) -> Result<Vec<Vec<u8>>> {
|
||||
Ok(get_twonly_flutter()?
|
||||
.user_discovery
|
||||
.get()
|
||||
.await
|
||||
.get_new_messages(contact_id, received_version)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn should_request_new_messages(contact_id: i64, version: &[u8]) -> Result<bool> {
|
||||
Ok(get_twonly_flutter()?
|
||||
.user_discovery
|
||||
.get()
|
||||
.await
|
||||
.should_request_new_messages(contact_id, version)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn handle_user_discovery_messages(
|
||||
contact_id: i64,
|
||||
messages: Vec<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
Ok(get_twonly_flutter()?
|
||||
.user_discovery
|
||||
.get()
|
||||
.await
|
||||
.handle_user_discovery_messages(contact_id, messages)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// use sqlx::types::chrono::{DateTime, Utc};
|
||||
use sqlx::FromRow;
|
||||
|
||||
use super::Database;
|
||||
use crate::bridge::error::Result;
|
||||
|
||||
#[derive(FromRow, Clone, Debug)]
|
||||
struct ContactRow {
|
||||
pub(crate) user_id: i64,
|
||||
pub(crate) username: String,
|
||||
// pub(crate) created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct Contact {
|
||||
pub user_id: i64,
|
||||
pub username: String,
|
||||
// pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl From<ContactRow> for Contact {
|
||||
fn from(row: ContactRow) -> Self {
|
||||
Self {
|
||||
user_id: row.user_id,
|
||||
username: row.username,
|
||||
// created_at: row.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Contact {
|
||||
pub(crate) async fn get_all_contacts(db: &Database) -> Result<Vec<Contact>> {
|
||||
let rows = sqlx::query_as::<_, ContactRow>("SELECT * FROM contacts")
|
||||
.fetch_all(&db.pool)
|
||||
.await?;
|
||||
Ok(rows.into_iter().map(Into::into).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bridge::tests::initialize_twonly_for_testing;
|
||||
use crate::database::contact::Contact;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_all_contacts() {
|
||||
let twonly = initialize_twonly_for_testing(true).await.unwrap();
|
||||
|
||||
let contacts = Contact::get_all_contacts(&twonly.database).await.unwrap();
|
||||
|
||||
let users = vec!["alice", "bob", "charlie", "david", "frank"];
|
||||
|
||||
assert_eq!(contacts.len(), users.len());
|
||||
|
||||
for contact in contacts {
|
||||
assert_eq!(users[contact.user_id as usize], &contact.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
pub(crate) mod contact;
|
||||
use crate::bridge::error::{Result, TwonlyError};
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||
|
|
@ -10,7 +9,7 @@ pub(crate) struct Database {
|
|||
}
|
||||
|
||||
impl Database {
|
||||
pub(crate) async fn new(db_path: &String) -> Result<Self> {
|
||||
pub(crate) async fn new(db_path: &String, read_only: bool) -> Result<Self> {
|
||||
let db_url = format!("sqlite://{}", db_path);
|
||||
|
||||
match Sqlite::database_exists(&db_url).await {
|
||||
|
|
@ -41,6 +40,7 @@ impl Database {
|
|||
let connect_options = format!("{db_url}?mode=rwc")
|
||||
.parse::<SqliteConnectOptions>()?
|
||||
.log_statements(log_statements_level)
|
||||
.read_only(read_only)
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.foreign_keys(true)
|
||||
.busy_timeout(Duration::from_millis(5000))
|
||||
|
|
@ -53,6 +53,13 @@ impl Database {
|
|||
.connect_with(connect_options)
|
||||
.await?;
|
||||
|
||||
let row: (String, String) = sqlx::query_as("SELECT sqlite_version(), sqlite_source_id()")
|
||||
.fetch_one(&pool)
|
||||
.await?;
|
||||
|
||||
tracing::info!("Rust SQLite Version: {}", row.0);
|
||||
tracing::info!("Rust SQLite Source ID: {}", row.1);
|
||||
|
||||
Ok(Self { pool: pool })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
pub mod bridge;
|
||||
mod database;
|
||||
mod frb_generated;
|
||||
mod user_discovery_store;
|
||||
mod standalone;
|
||||
mod utils;
|
||||
|
|
|
|||
9
rust/src/standalone/mod.rs
Normal file
9
rust/src/standalone/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use crate::{bridge::TwonlyConfig, database::Database};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) struct TwonlyStandalone {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) config: TwonlyConfig,
|
||||
/// Because Rust is called from a different process it is safe to write to the twonly_db.
|
||||
pub(crate) twonly_db: Arc<Database>,
|
||||
}
|
||||
|
|
@ -1,349 +0,0 @@
|
|||
#[allow(async_fn_in_trait)]
|
||||
use protocols::user_discovery::error::{Result, UserDiscoveryError};
|
||||
use protocols::user_discovery::traits::{AnnouncedUser, OtherPromotion, UserDiscoveryStore};
|
||||
use protocols::user_discovery::UserID;
|
||||
use sqlx::{QueryBuilder, Row, Sqlite, Transaction};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::bridge::error::TwonlyError;
|
||||
use crate::bridge::{get_workspace, Twonly};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct UserDiscoveryDatabaseStore {
|
||||
ws: Arc<&'static Twonly>,
|
||||
}
|
||||
|
||||
impl UserDiscoveryStore for UserDiscoveryDatabaseStore {
|
||||
async fn new() -> Self {
|
||||
#[cfg(test)]
|
||||
return Self {
|
||||
ws: Arc::new(
|
||||
crate::bridge::tests::initialize_twonly_for_testing(false)
|
||||
.await
|
||||
.unwrap(),
|
||||
),
|
||||
};
|
||||
#[allow(unreachable_code)]
|
||||
Self {
|
||||
ws: Arc::new(get_workspace().unwrap()),
|
||||
}
|
||||
}
|
||||
async fn get_config(&self) -> Result<String> {
|
||||
let config_path =
|
||||
PathBuf::from(&self.ws.config.data_directory).join("user_discovery_config.json");
|
||||
|
||||
if !config_path.is_file() {
|
||||
return Err(UserDiscoveryError::NotInitialized);
|
||||
}
|
||||
|
||||
tracing::debug!("Loading Config from {}", config_path.display());
|
||||
Ok(std::fs::read_to_string(&config_path)?)
|
||||
}
|
||||
|
||||
async fn update_config(&self, update: String) -> Result<()> {
|
||||
tracing::debug!("Updating configuration file.");
|
||||
let config_path =
|
||||
PathBuf::from(&self.ws.config.data_directory).join("user_discovery_config.json");
|
||||
std::fs::write(config_path, &update)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_shares(&self, shares: Vec<Vec<u8>>) -> Result<()> {
|
||||
let mut query_builder: QueryBuilder<Sqlite> =
|
||||
QueryBuilder::new("INSERT INTO user_discovery_shares (share) ");
|
||||
|
||||
query_builder.push_values(shares, |mut b, share| {
|
||||
b.push_bind(share);
|
||||
});
|
||||
|
||||
query_builder
|
||||
.build()
|
||||
.execute(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_share_for_contact(&self, contact_id: UserID) -> Result<Vec<u8>> {
|
||||
let mut tx: Transaction<'_, Sqlite> = self
|
||||
.ws
|
||||
.database
|
||||
.pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
// 1. Check if the user already has a share assigned
|
||||
let existing: Option<Vec<u8>> =
|
||||
sqlx::query_scalar("SELECT share FROM user_discovery_shares WHERE contact_id = ?")
|
||||
.bind(contact_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
if let Some(share) = existing {
|
||||
tx.commit().await.map_err(TwonlyError::from)?;
|
||||
return Ok(share);
|
||||
}
|
||||
|
||||
// 2. No share found. Try to assign an available one (where contact_id is NULL)
|
||||
let rows_affected = sqlx::query(
|
||||
"UPDATE user_discovery_shares
|
||||
SET contact_id = ?
|
||||
WHERE share_id = (
|
||||
SELECT share_id FROM user_discovery_shares
|
||||
WHERE contact_id IS NULL
|
||||
LIMIT 1
|
||||
)",
|
||||
)
|
||||
.bind(contact_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?
|
||||
.rows_affected();
|
||||
|
||||
if rows_affected == 0 {
|
||||
return Err(UserDiscoveryError::NoSharesLeft);
|
||||
}
|
||||
|
||||
// 3. Retrieve the newly assigned share
|
||||
let assigned_share: Vec<u8> =
|
||||
sqlx::query_scalar("SELECT share FROM user_discovery_shares WHERE contact_id = ?")
|
||||
.bind(contact_id)
|
||||
.fetch_one(&mut *tx)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
tx.commit().await.map_err(TwonlyError::from)?;
|
||||
Ok(assigned_share)
|
||||
}
|
||||
|
||||
async fn push_own_promotion(
|
||||
&self,
|
||||
contact_id: UserID,
|
||||
version: u32,
|
||||
promotion: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO user_discovery_own_promotions (contact_id, promotion, version_id)
|
||||
VALUES (?1, ?2, ?3)
|
||||
"#,
|
||||
)
|
||||
.bind(contact_id)
|
||||
.bind(promotion)
|
||||
.bind(version as i64) // SQLite integers are usually i32/i64
|
||||
.execute(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_own_promotions_after_version(&self, version: u32) -> Result<Vec<Vec<u8>>> {
|
||||
let promotions: Vec<Vec<u8>> = sqlx::query_scalar(
|
||||
"SELECT promotion FROM user_discovery_own_promotions
|
||||
WHERE version_id > ?
|
||||
ORDER BY version_id ASC",
|
||||
)
|
||||
.bind(version as i64)
|
||||
.fetch_all(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
Ok(promotions)
|
||||
}
|
||||
|
||||
async fn store_other_promotion(&self, promotion: OtherPromotion) -> Result<()> {
|
||||
sqlx::query(
|
||||
r"
|
||||
INSERT INTO user_discovery_other_promotions (
|
||||
from_contact_id,
|
||||
promotion_id,
|
||||
public_id,
|
||||
threshold,
|
||||
announcement_share,
|
||||
public_key_verified_timestamp
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||
",
|
||||
)
|
||||
.bind(promotion.from_contact_id)
|
||||
.bind(promotion.promotion_id as i64)
|
||||
.bind(promotion.public_id)
|
||||
.bind(promotion.threshold as i64)
|
||||
.bind(promotion.announcement_share)
|
||||
.bind(promotion.public_key_verified_timestamp) // Option<i64> maps to NULLable
|
||||
.execute(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_other_promotions_by_public_id(
|
||||
&self,
|
||||
public_id: i64,
|
||||
) -> Result<Vec<OtherPromotion>> {
|
||||
let promotions = sqlx::query_as::<_, OtherPromotion>(
|
||||
"SELECT * FROM user_discovery_other_promotions WHERE public_id = ?",
|
||||
)
|
||||
.bind(public_id)
|
||||
.fetch_all(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
Ok(promotions)
|
||||
}
|
||||
|
||||
async fn get_announced_user_by_public_id(
|
||||
&self,
|
||||
public_id: i64,
|
||||
) -> Result<Option<AnnouncedUser>> {
|
||||
let row = sqlx::query("SELECT * FROM user_discovery_announced_users WHERE public_id = ?")
|
||||
.bind(public_id)
|
||||
.fetch_optional(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
match row {
|
||||
Some(r) => Ok(Some(AnnouncedUser {
|
||||
user_id: r.get::<i64, _>("announced_user_id"),
|
||||
public_key: r.get::<Vec<u8>, _>("announced_public_key"),
|
||||
public_id: r.get::<i64, _>("public_id"),
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn push_new_user_relation(
|
||||
&self,
|
||||
from_contact_id: UserID,
|
||||
announced_user: AnnouncedUser,
|
||||
public_key_verified_timestamp: Option<i64>,
|
||||
) -> Result<()> {
|
||||
let mut tx = self
|
||||
.ws
|
||||
.database
|
||||
.pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
sqlx::query(
|
||||
r"
|
||||
INSERT INTO user_discovery_announced_users (announced_user_id, announced_public_key, public_id)
|
||||
VALUES (?1, ?2, ?3)
|
||||
ON CONFLICT(announced_user_id) DO NOTHING
|
||||
")
|
||||
.bind(announced_user.user_id)
|
||||
.bind(announced_user.public_key)
|
||||
.bind(announced_user.public_id)
|
||||
.execute(&mut *tx)
|
||||
.await.map_err(TwonlyError::from)?;
|
||||
|
||||
if from_contact_id != announced_user.user_id {
|
||||
tracing::debug!(
|
||||
"INSERTING THAT {} KNOWS {}",
|
||||
from_contact_id,
|
||||
announced_user.user_id
|
||||
);
|
||||
sqlx::query(
|
||||
r"INSERT INTO user_discovery_user_relations (
|
||||
announced_user_id,
|
||||
from_contact_id,
|
||||
public_key_verified_timestamp
|
||||
)
|
||||
VALUES (?1, ?2, ?3)
|
||||
ON CONFLICT(announced_user_id, from_contact_id) DO UPDATE SET
|
||||
public_key_verified_timestamp = excluded.public_key_verified_timestamp
|
||||
",
|
||||
)
|
||||
.bind(announced_user.user_id)
|
||||
.bind(from_contact_id)
|
||||
.bind(public_key_verified_timestamp)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
}
|
||||
|
||||
tx.commit().await.map_err(TwonlyError::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_all_announced_users(
|
||||
&self,
|
||||
) -> Result<HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>> {
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
u.announced_user_id,
|
||||
u.announced_public_key,
|
||||
u.public_id,
|
||||
r.from_contact_id,
|
||||
r.public_key_verified_timestamp
|
||||
FROM user_discovery_announced_users u
|
||||
LEFT JOIN user_discovery_user_relations r
|
||||
ON u.announced_user_id = r.announced_user_id
|
||||
ORDER BY u.announced_user_id
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
let mut map: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>> = HashMap::new();
|
||||
|
||||
for row in rows {
|
||||
let announced_user = AnnouncedUser {
|
||||
user_id: row.get::<i64, _>("announced_user_id"),
|
||||
public_key: row.get::<Vec<u8>, _>("announced_public_key"),
|
||||
public_id: row.get::<i64, _>("public_id"),
|
||||
};
|
||||
|
||||
let relations_list = map.entry(announced_user).or_insert_with(Vec::new);
|
||||
|
||||
// SQLX returns NULL for columns in a LEFT JOIN where no match is found.
|
||||
if let Ok(Some(contact_id)) = row.try_get::<Option<i64>, _>("from_contact_id") {
|
||||
let timestamp = row.get::<Option<i64>, _>("public_key_verified_timestamp");
|
||||
relations_list.push((contact_id, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
async fn get_contact_version(&self, contact_id: UserID) -> Result<Option<Vec<u8>>> {
|
||||
let version: Option<Vec<u8>> =
|
||||
sqlx::query_scalar("SELECT user_discovery_version FROM contacts WHERE user_id = ?")
|
||||
.bind(contact_id)
|
||||
.fetch_optional(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
async fn set_contact_version(&self, contact_id: UserID, update: Vec<u8>) -> Result<()> {
|
||||
sqlx::query("UPDATE contacts SET user_discovery_version = ? WHERE user_id = ?")
|
||||
.bind(update)
|
||||
.bind(contact_id)
|
||||
.execute(&self.ws.database.pool)
|
||||
.await
|
||||
.map_err(TwonlyError::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::user_discovery_store::UserDiscoveryDatabaseStore;
|
||||
use protocols::user_discovery::tests::test_initialize_user_discovery;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_initialize_user_discovery_database_store() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
test_initialize_user_discovery::<UserDiscoveryDatabaseStore>().await;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,16 @@
|
|||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct Shared<T>(Arc<RwLock<T>>);
|
||||
impl<T> Shared<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
impl<T> Shared<T> {
|
||||
pub(crate) fn new(value: T) -> Self {
|
||||
Self(Arc::new(RwLock::new(value)))
|
||||
}
|
||||
pub(crate) fn get(&self) -> RwLockReadGuard<'_, T> {
|
||||
self.0.read()
|
||||
}
|
||||
pub(crate) fn cloned(&self) -> T {
|
||||
self.0.read().clone()
|
||||
}
|
||||
pub(crate) fn set(&self, value: T) {
|
||||
*self.0.write() = value;
|
||||
pub(crate) async fn get(&self) -> RwLockReadGuard<'_, T> {
|
||||
self.0.read().await
|
||||
}
|
||||
// pub(crate) async fn set(&self, value: T) {
|
||||
// *self.0.write().await = value;
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ impl InMemoryStore {
|
|||
}
|
||||
|
||||
impl UserDiscoveryStore for InMemoryStore {
|
||||
async fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
async fn get_config(&self) -> Result<String> {
|
||||
if let Some(storage) = self.storage().config.clone() {
|
||||
return Ok(storage);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ fn get_version_bytes(announcement: u32, promotion: u32) -> Vec<u8> {
|
|||
.encode_to_vec()
|
||||
}
|
||||
|
||||
async fn get_ud<S: UserDiscoveryStore + Clone>(user_id: usize) -> UserDiscovery<S, TestingUtils> {
|
||||
let store = S::new().await;
|
||||
async fn get_ud<S: UserDiscoveryStore + Clone + Default>(
|
||||
user_id: usize,
|
||||
) -> UserDiscovery<S, TestingUtils> {
|
||||
let store = S::default();
|
||||
let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap();
|
||||
|
||||
ud.initialize_or_update(2, user_id as UserID, vec![user_id as u8; 32])
|
||||
|
|
@ -97,7 +99,7 @@ struct TestUsers<S: UserDiscoveryStore> {
|
|||
uds: Vec<UserDiscovery<S, TestingUtils>>,
|
||||
}
|
||||
|
||||
impl<S: UserDiscoveryStore + Clone> TestUsers<S> {
|
||||
impl<S: UserDiscoveryStore + Clone + Default> TestUsers<S> {
|
||||
async fn get() -> Self {
|
||||
let names = ["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"];
|
||||
let mut uds = vec![];
|
||||
|
|
@ -119,7 +121,7 @@ impl<S: UserDiscoveryStore + Clone> TestUsers<S> {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn test_initialize_user_discovery<S: UserDiscoveryStore + Clone>() {
|
||||
pub async fn test_initialize_user_discovery<S: UserDiscoveryStore + Clone + Default>() {
|
||||
#[cfg(test)]
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ pub struct AnnouncedUser {
|
|||
}
|
||||
|
||||
pub trait UserDiscoveryStore {
|
||||
fn new() -> impl std::future::Future<Output = Self> + Send;
|
||||
fn get_config(&self) -> impl Future<Output = Result<String>> + Send;
|
||||
fn update_config(&self, update: String) -> impl Future<Output = Result<()>> + Send;
|
||||
fn set_shares(&self, shares: Vec<Vec<u8>>) -> impl Future<Output = Result<()>> + Send;
|
||||
|
|
@ -88,6 +87,9 @@ pub trait UserDiscoveryUtils {
|
|||
pubkey: &[u8],
|
||||
signature: &[u8],
|
||||
) -> impl Future<Output = Result<bool>> + Send;
|
||||
/// In case the the user does not exists yet return false.
|
||||
/// If this happens this should trigger an error, as this functions is only when a message was received from this user...
|
||||
/// This is used to verify that the share of the promotions contains the same public key and the user is not secretly announcing a different one
|
||||
fn verify_stored_pubkey(
|
||||
&self,
|
||||
from_contact_id: UserID,
|
||||
|
|
|
|||
Loading…
Reference in a new issue