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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
|
|
||||||
import 'database/contact.dart';
|
|
||||||
import 'frb_generated.dart';
|
import 'frb_generated.dart';
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `get_workspace`
|
// 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)]`: `Twonly`
|
// 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}) =>
|
Future<void> initializeTwonlyFlutter({required TwonlyConfig config}) =>
|
||||||
RustLib.instance.api.crateBridgeInitializeTwonly(config: config);
|
RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config);
|
||||||
|
|
||||||
Future<List<Contact>> getAllContacts() =>
|
class AnnouncedUser {
|
||||||
RustLib.instance.api.crateBridgeGetAllContacts();
|
const AnnouncedUser({
|
||||||
|
required this.userId,
|
||||||
|
required this.publicKey,
|
||||||
|
required this.publicId,
|
||||||
|
});
|
||||||
|
final PlatformInt64 userId;
|
||||||
|
final Uint8List publicKey;
|
||||||
|
final PlatformInt64 publicId;
|
||||||
|
|
||||||
Future<OtherPromotion> loadPromotions() =>
|
@override
|
||||||
RustLib.instance.api.crateBridgeLoadPromotions();
|
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 {
|
class OtherPromotion {
|
||||||
const 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.
|
// This file is automatically generated, so please do not edit it.
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
// @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:async';
|
||||||
import 'dart:convert';
|
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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||||
|
|
||||||
import 'bridge.dart';
|
import 'bridge.dart';
|
||||||
import 'database/contact.dart';
|
import 'bridge/callbacks.dart';
|
||||||
|
import 'bridge/wrapper/user_discovery.dart';
|
||||||
import 'frb_generated.dart';
|
import 'frb_generated.dart';
|
||||||
|
|
||||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
|
|
@ -24,9 +25,96 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
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
|
@protected
|
||||||
String dco_decode_String(dynamic raw);
|
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
|
@protected
|
||||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
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);
|
TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Contact dco_decode_contact(dynamic raw);
|
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@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
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
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
|
@protected
|
||||||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -63,12 +172,34 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void dco_decode_unit(dynamic raw);
|
void dco_decode_unit(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BigInt dco_decode_usize(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
String sse_decode_String(SseDeserializer deserializer);
|
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
|
@protected
|
||||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -78,20 +209,53 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Contact sse_decode_contact(SseDeserializer deserializer);
|
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@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
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
AnnouncedUser? sse_decode_opt_box_autoadd_announced_user(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
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
|
@protected
|
||||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -108,10 +272,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
void sse_decode_unit(SseDeserializer deserializer);
|
void sse_decode_unit(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_i_32(SseDeserializer deserializer);
|
BigInt sse_decode_usize(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
bool sse_decode_bool(SseDeserializer deserializer);
|
int sse_decode_i_32(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_AnyhowException(
|
void sse_encode_AnyhowException(
|
||||||
|
|
@ -119,9 +283,112 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
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
|
@protected
|
||||||
void sse_encode_String(String self, SseSerializer serializer);
|
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
|
@protected
|
||||||
void sse_encode_box_autoadd_i_64(
|
void sse_encode_box_autoadd_i_64(
|
||||||
PlatformInt64 self,
|
PlatformInt64 self,
|
||||||
|
|
@ -135,13 +402,31 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_contact(Contact self, SseSerializer serializer);
|
void sse_encode_flutter_user_discovery(
|
||||||
|
FlutterUserDiscovery self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@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
|
@protected
|
||||||
void sse_encode_list_prim_u_8_strict(
|
void sse_encode_list_prim_u_8_strict(
|
||||||
|
|
@ -149,12 +434,36 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_opt_box_autoadd_announced_user(
|
||||||
|
AnnouncedUser? self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_i_64(
|
void sse_encode_opt_box_autoadd_i_64(
|
||||||
PlatformInt64? self,
|
PlatformInt64? self,
|
||||||
SseSerializer serializer,
|
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
|
@protected
|
||||||
void sse_encode_other_promotion(
|
void sse_encode_other_promotion(
|
||||||
OtherPromotion self,
|
OtherPromotion self,
|
||||||
|
|
@ -174,10 +483,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
void sse_encode_unit(void self, SseSerializer serializer);
|
void sse_encode_unit(void self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: wire_class
|
// Section: wire_class
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
// This file is automatically generated, so please do not edit it.
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
// @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
|
// 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 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
||||||
|
|
||||||
import 'bridge.dart';
|
import 'bridge.dart';
|
||||||
import 'database/contact.dart';
|
import 'bridge/callbacks.dart';
|
||||||
|
import 'bridge/wrapper/user_discovery.dart';
|
||||||
import 'frb_generated.dart';
|
import 'frb_generated.dart';
|
||||||
|
|
||||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
|
|
@ -25,9 +26,96 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
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
|
@protected
|
||||||
String dco_decode_String(dynamic raw);
|
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
|
@protected
|
||||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
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);
|
TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Contact dco_decode_contact(dynamic raw);
|
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@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
|
@protected
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
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
|
@protected
|
||||||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
||||||
|
|
||||||
|
|
@ -64,12 +173,34 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
void dco_decode_unit(dynamic raw);
|
void dco_decode_unit(dynamic raw);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
BigInt dco_decode_usize(dynamic raw);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
String sse_decode_String(SseDeserializer deserializer);
|
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
|
@protected
|
||||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -79,20 +210,53 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Contact sse_decode_contact(SseDeserializer deserializer);
|
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@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
|
@protected
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
AnnouncedUser? sse_decode_opt_box_autoadd_announced_user(
|
||||||
|
SseDeserializer deserializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
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
|
@protected
|
||||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
||||||
|
|
||||||
|
|
@ -109,10 +273,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
void sse_decode_unit(SseDeserializer deserializer);
|
void sse_decode_unit(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
int sse_decode_i_32(SseDeserializer deserializer);
|
BigInt sse_decode_usize(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
bool sse_decode_bool(SseDeserializer deserializer);
|
int sse_decode_i_32(SseDeserializer deserializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_AnyhowException(
|
void sse_encode_AnyhowException(
|
||||||
|
|
@ -120,9 +284,112 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
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
|
@protected
|
||||||
void sse_encode_String(String self, SseSerializer serializer);
|
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
|
@protected
|
||||||
void sse_encode_box_autoadd_i_64(
|
void sse_encode_box_autoadd_i_64(
|
||||||
PlatformInt64 self,
|
PlatformInt64 self,
|
||||||
|
|
@ -136,13 +403,31 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_contact(Contact self, SseSerializer serializer);
|
void sse_encode_flutter_user_discovery(
|
||||||
|
FlutterUserDiscovery self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@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
|
@protected
|
||||||
void sse_encode_list_prim_u_8_strict(
|
void sse_encode_list_prim_u_8_strict(
|
||||||
|
|
@ -150,12 +435,36 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
void sse_encode_opt_box_autoadd_announced_user(
|
||||||
|
AnnouncedUser? self,
|
||||||
|
SseSerializer serializer,
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_opt_box_autoadd_i_64(
|
void sse_encode_opt_box_autoadd_i_64(
|
||||||
PlatformInt64? self,
|
PlatformInt64? self,
|
||||||
SseSerializer serializer,
|
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
|
@protected
|
||||||
void sse_encode_other_promotion(
|
void sse_encode_other_promotion(
|
||||||
OtherPromotion self,
|
OtherPromotion self,
|
||||||
|
|
@ -175,10 +484,10 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
void sse_encode_unit(void self, SseSerializer serializer);
|
void sse_encode_unit(void self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section: wire_class
|
// Section: wire_class
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import 'package:twonly/app.dart';
|
||||||
import 'package:twonly/core/bridge.dart' as bridge;
|
import 'package:twonly/core/bridge.dart' as bridge;
|
||||||
import 'package:twonly/core/frb_generated.dart';
|
import 'package:twonly/core/frb_generated.dart';
|
||||||
import 'package:twonly/globals.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/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||||
|
|
@ -37,7 +38,11 @@ void main() async {
|
||||||
globalApplicationSupportDirectory =
|
globalApplicationSupportDirectory =
|
||||||
(await getApplicationSupportDirectory()).path;
|
(await getApplicationSupportDirectory()).path;
|
||||||
|
|
||||||
await bridge.initializeTwonly(
|
initLogger();
|
||||||
|
|
||||||
|
await initFlutterCallbacksForRust();
|
||||||
|
|
||||||
|
await bridge.initializeTwonlyFlutter(
|
||||||
config: bridge.TwonlyConfig(
|
config: bridge.TwonlyConfig(
|
||||||
databasePath: '$globalApplicationSupportDirectory/twonly.sqlite',
|
databasePath: '$globalApplicationSupportDirectory/twonly.sqlite',
|
||||||
dataDirectory: globalApplicationSupportDirectory,
|
dataDirectory: globalApplicationSupportDirectory,
|
||||||
|
|
@ -68,8 +73,6 @@ void main() async {
|
||||||
await deleteLocalUserData();
|
await deleteLocalUserData();
|
||||||
}
|
}
|
||||||
|
|
||||||
initLogger();
|
|
||||||
|
|
||||||
final settingsController = SettingsChangeProvider();
|
final settingsController = SettingsChangeProvider();
|
||||||
|
|
||||||
await settingsController.loadSettings();
|
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 settingsPrivacy = '/settings/privacy';
|
||||||
static const String settingsPrivacyBlockUsers =
|
static const String settingsPrivacyBlockUsers =
|
||||||
'/settings/privacy/block_users';
|
'/settings/privacy/block_users';
|
||||||
|
static const String settingsPrivacyUserDiscovery =
|
||||||
|
'/settings/privacy/user_discovery';
|
||||||
static const String settingsNotification = '/settings/notification';
|
static const String settingsNotification = '/settings/notification';
|
||||||
static const String settingsStorage = '/settings/storage_data';
|
static const String settingsStorage = '/settings/storage_data';
|
||||||
static const String settingsStorageImport = '/settings/storage_data/import';
|
static const String settingsStorageImport = '/settings/storage_data/import';
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool isUserDiscoveryEnabled = false;
|
bool isUserDiscoveryEnabled = false;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
int minimumRequiredImagesExchanged = 4;
|
||||||
|
|
||||||
// -- Custom DATA --
|
// -- Custom DATA --
|
||||||
|
|
||||||
@JsonKey(defaultValue: 100_000)
|
@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/help/help.view.dart';
|
||||||
import 'package:twonly/src/views/settings/notification.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.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/modify_avatar.view.dart';
|
||||||
import 'package:twonly/src/views/settings/profile/profile.view.dart';
|
import 'package:twonly/src/views/settings/profile/profile.view.dart';
|
||||||
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
||||||
|
|
@ -200,7 +201,11 @@ final routerProvider = GoRouter(
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'block_users',
|
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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final primaryColor = const Color(0xFF57CC99);
|
||||||
|
|
||||||
final ThemeData lightTheme = ThemeData(
|
final ThemeData lightTheme = ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
seedColor: const Color(0xFF57CC99),
|
seedColor: primaryColor,
|
||||||
),
|
),
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
inputDecorationTheme: const InputDecorationTheme(
|
||||||
border: OutlineInputBorder(),
|
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();
|
converter.close();
|
||||||
return sha256Sink.events.single.bytes;
|
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(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text('Freunde finden'),
|
||||||
|
onTap: () async {
|
||||||
|
await context.push(Routes.settingsPrivacyUserDiscovery);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsTypingIndication),
|
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/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
||||||
|
|
||||||
class PrivacyViewBlockUsersView extends StatefulWidget {
|
class BlockUsersView extends StatefulWidget {
|
||||||
const PrivacyViewBlockUsersView({super.key});
|
const BlockUsersView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PrivacyViewBlockUsersView> createState() => _PrivacyViewBlockUsers();
|
State<BlockUsersView> createState() => _PrivacyViewBlockUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsersView> {
|
class _PrivacyViewBlockUsers extends State<BlockUsersView> {
|
||||||
late Stream<List<Contact>> allUsers;
|
late Stream<List<Contact>> allUsers;
|
||||||
List<Contact> filteredUsers = [];
|
List<Contact> filteredUsers = [];
|
||||||
String filter = '';
|
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"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift
|
name: drift
|
||||||
sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e"
|
sha256: "055c249d1f91be5a47fe447f88afc24c4ca6f4cd6c5ed66767b4797d48acc2e5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.31.0"
|
version: "2.32.1"
|
||||||
drift_dev:
|
drift_dev:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: drift_dev
|
name: drift_dev
|
||||||
sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587"
|
sha256: "88a9de3af8571518148a6d8a513b57779fd1e60a026d3ab8a481a878fba01d91"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.31.0"
|
version: "2.32.1"
|
||||||
drift_flutter:
|
drift_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: drift_flutter
|
name: drift_flutter
|
||||||
sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7
|
sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.8"
|
version: "0.3.0"
|
||||||
ed25519_edwards:
|
ed25519_edwards:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1809,30 +1809,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.0"
|
||||||
sqlite3:
|
sqlcipher_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlcipher_flutter_libs
|
||||||
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
|
sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
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:
|
sqlite3_flutter_libs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
|
sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.42"
|
version: "0.6.0+eol"
|
||||||
sqlparser:
|
sqlparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlparser
|
name: sqlparser
|
||||||
sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19"
|
sha256: ab2b467425f1d4f3acfa5fd11a08226f7d6c26ff102c06be1807e1dff34e050b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.43.1"
|
version: "0.44.3"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,8 @@ dependencies:
|
||||||
json_annotation: ^4.9.0 # google.dev
|
json_annotation: ^4.9.0 # google.dev
|
||||||
protobuf: ^4.0.0 # google.dev
|
protobuf: ^4.0.0 # google.dev
|
||||||
scrollable_positioned_list: ^0.3.8 # google.dev
|
scrollable_positioned_list: ^0.3.8 # google.dev
|
||||||
drift: ^2.25.1
|
drift: ^2.32.0
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.3.0
|
||||||
sqlite3: ^2.9.4
|
|
||||||
|
|
||||||
|
|
||||||
# Flutter Favorite
|
# 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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
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]]
|
[[package]]
|
||||||
name = "crossbeam-queue"
|
name = "crossbeam-queue"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
|
|
@ -392,6 +401,15 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -1071,9 +1089,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.35.0"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
|
@ -1107,6 +1125,15 @@ version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
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]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
|
@ -1149,6 +1176,15 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
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]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
|
|
@ -1165,6 +1201,12 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.46"
|
version = "0.1.46"
|
||||||
|
|
@ -1260,6 +1302,12 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
@ -1340,6 +1388,12 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
|
@ -1582,7 +1636,7 @@ name = "rust_lib_twonly"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flutter_rust_bridge",
|
"flutter_rust_bridge",
|
||||||
"parking_lot",
|
"paste",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"protocols",
|
"protocols",
|
||||||
|
|
@ -1592,6 +1646,8 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1725,6 +1781,15 @@ dependencies = [
|
||||||
"digest 0.11.2",
|
"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]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -2009,6 +2074,12 @@ version = "2.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symlink"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
|
|
@ -2073,6 +2144,15 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "threadpool"
|
name = "threadpool"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
|
@ -2082,6 +2162,37 @@ dependencies = [
|
||||||
"num_cpus",
|
"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]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -2158,6 +2269,19 @@ dependencies = [
|
||||||
"tracing-core",
|
"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]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.31"
|
version = "0.1.31"
|
||||||
|
|
@ -2176,6 +2300,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
|
|
@ -2235,6 +2389,12 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,35 @@
|
||||||
[workspace]
|
[package]
|
||||||
members = ["protocols", "core"]
|
name = "rust_lib_twonly"
|
||||||
resolver = "3"
|
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 {
|
pub enum TwonlyError {
|
||||||
#[error("global twonly is not initialized")]
|
#[error("global twonly is not initialized")]
|
||||||
Initialization,
|
Initialization,
|
||||||
|
#[error("init_flutter_callbacks was not called")]
|
||||||
|
MissingCallbackInitialization,
|
||||||
#[error("Could not find the given database")]
|
#[error("Could not find the given database")]
|
||||||
DatabaseNotFound,
|
DatabaseNotFound,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
UserDiscoveryError(#[from] UserDiscoveryError),
|
||||||
|
#[error("Error in dart callback")]
|
||||||
|
DartError,
|
||||||
|
#[error("{0}")]
|
||||||
SqliteError(#[from] sqlx::Error),
|
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)]
|
#![allow(unexpected_cfgs)]
|
||||||
|
pub mod callbacks;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod user_discovery_utils;
|
pub mod log;
|
||||||
use crate::bridge::user_discovery_utils::UserDiscoveryUtilsFlutter;
|
pub mod wrapper;
|
||||||
use crate::database::contact::Contact;
|
|
||||||
use crate::database::Database;
|
use crate::bridge::callbacks::user_discovery::{
|
||||||
use crate::user_discovery_store::UserDiscoveryDatabaseStore;
|
UserDiscoveryStoreFlutter, UserDiscoveryUtilsFlutter,
|
||||||
|
};
|
||||||
|
use crate::bridge::log::init_tracing;
|
||||||
use crate::utils::Shared;
|
use crate::utils::Shared;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use error::TwonlyError;
|
use error::TwonlyError;
|
||||||
use flutter_rust_bridge::frb;
|
use flutter_rust_bridge::frb;
|
||||||
use protocols::user_discovery::UserDiscovery;
|
use protocols::user_discovery::UserDiscovery;
|
||||||
use std::sync::Arc;
|
use std::path::PathBuf;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
pub use protocols::user_discovery::traits::AnnouncedUser;
|
||||||
pub use protocols::user_discovery::traits::OtherPromotion;
|
pub use protocols::user_discovery::traits::OtherPromotion;
|
||||||
|
|
||||||
#[frb(mirror(OtherPromotion))]
|
#[frb(mirror(OtherPromotion))]
|
||||||
|
|
@ -25,34 +29,56 @@ pub struct _OtherPromotion {
|
||||||
pub public_key_verified_timestamp: Option<i64>,
|
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 struct TwonlyConfig {
|
||||||
pub database_path: String,
|
pub database_path: String,
|
||||||
pub data_directory: String,
|
pub data_directory: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Twonly {
|
pub(crate) struct TwonlyFlutter {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) config: TwonlyConfig,
|
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:
|
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)
|
GLOBAL_TWONLY.get().ok_or(TwonlyError::Initialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
pub async fn initialize_twonly_flutter(config: TwonlyConfig) -> Result<()> {
|
||||||
tracing::debug!("Initialized twonly workspace.");
|
let log_dir = PathBuf::from(&config.data_directory).join("log");
|
||||||
let twonly_res: Result<&'static Twonly> = GLOBAL_TWONLY
|
init_tracing(&log_dir, true).await;
|
||||||
|
tracing::info!("Initialized twonly workspace.");
|
||||||
|
let twonly_res: Result<&'static TwonlyFlutter> = GLOBAL_TWONLY
|
||||||
.get_or_try_init(|| async {
|
.get_or_try_init(|| async {
|
||||||
let database = Arc::new(Database::new(&config.database_path).await?);
|
// let database_dir = PathBuf::from(&config.database_path.clone());
|
||||||
Ok(Twonly {
|
// 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,
|
config,
|
||||||
database,
|
// twonly_db_readonly,
|
||||||
user_discovery: Shared::default(),
|
// rust_db,
|
||||||
|
user_discovery: Shared::new(UserDiscovery::new(
|
||||||
|
UserDiscoveryStoreFlutter {},
|
||||||
|
UserDiscoveryUtilsFlutter {},
|
||||||
|
)?),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -61,102 +87,3 @@ pub async fn initialize_twonly(config: TwonlyConfig) -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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 crate::bridge::error::{Result, TwonlyError};
|
||||||
use sqlx::migrate::MigrateDatabase;
|
use sqlx::migrate::MigrateDatabase;
|
||||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||||
|
|
@ -10,7 +9,7 @@ pub(crate) struct Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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);
|
let db_url = format!("sqlite://{}", db_path);
|
||||||
|
|
||||||
match Sqlite::database_exists(&db_url).await {
|
match Sqlite::database_exists(&db_url).await {
|
||||||
|
|
@ -41,6 +40,7 @@ impl Database {
|
||||||
let connect_options = format!("{db_url}?mode=rwc")
|
let connect_options = format!("{db_url}?mode=rwc")
|
||||||
.parse::<SqliteConnectOptions>()?
|
.parse::<SqliteConnectOptions>()?
|
||||||
.log_statements(log_statements_level)
|
.log_statements(log_statements_level)
|
||||||
|
.read_only(read_only)
|
||||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||||
.foreign_keys(true)
|
.foreign_keys(true)
|
||||||
.busy_timeout(Duration::from_millis(5000))
|
.busy_timeout(Duration::from_millis(5000))
|
||||||
|
|
@ -53,6 +53,13 @@ impl Database {
|
||||||
.connect_with(connect_options)
|
.connect_with(connect_options)
|
||||||
.await?;
|
.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 })
|
Ok(Self { pool: pool })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
||||||
pub mod bridge;
|
pub mod bridge;
|
||||||
mod database;
|
mod database;
|
||||||
mod frb_generated;
|
mod frb_generated;
|
||||||
mod user_discovery_store;
|
mod standalone;
|
||||||
mod utils;
|
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 std::sync::Arc;
|
||||||
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub(crate) struct Shared<T>(Arc<RwLock<T>>);
|
pub(crate) struct Shared<T>(Arc<RwLock<T>>);
|
||||||
impl<T> Shared<T>
|
impl<T> Shared<T> {
|
||||||
where
|
|
||||||
T: Clone,
|
|
||||||
{
|
|
||||||
pub(crate) fn new(value: T) -> Self {
|
pub(crate) fn new(value: T) -> Self {
|
||||||
Self(Arc::new(RwLock::new(value)))
|
Self(Arc::new(RwLock::new(value)))
|
||||||
}
|
}
|
||||||
pub(crate) fn get(&self) -> RwLockReadGuard<'_, T> {
|
pub(crate) async fn get(&self) -> RwLockReadGuard<'_, T> {
|
||||||
self.0.read()
|
self.0.read().await
|
||||||
}
|
|
||||||
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 set(&self, value: T) {
|
||||||
|
// *self.0.write().await = value;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,6 @@ impl InMemoryStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDiscoveryStore for InMemoryStore {
|
impl UserDiscoveryStore for InMemoryStore {
|
||||||
async fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
async fn get_config(&self) -> Result<String> {
|
async fn get_config(&self) -> Result<String> {
|
||||||
if let Some(storage) = self.storage().config.clone() {
|
if let Some(storage) = self.storage().config.clone() {
|
||||||
return Ok(storage);
|
return Ok(storage);
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ fn get_version_bytes(announcement: u32, promotion: u32) -> Vec<u8> {
|
||||||
.encode_to_vec()
|
.encode_to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_ud<S: UserDiscoveryStore + Clone>(user_id: usize) -> UserDiscovery<S, TestingUtils> {
|
async fn get_ud<S: UserDiscoveryStore + Clone + Default>(
|
||||||
let store = S::new().await;
|
user_id: usize,
|
||||||
|
) -> UserDiscovery<S, TestingUtils> {
|
||||||
|
let store = S::default();
|
||||||
let ud = UserDiscovery::new(store.to_owned(), TestingUtils::default()).unwrap();
|
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])
|
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>>,
|
uds: Vec<UserDiscovery<S, TestingUtils>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: UserDiscoveryStore + Clone> TestUsers<S> {
|
impl<S: UserDiscoveryStore + Clone + Default> TestUsers<S> {
|
||||||
async fn get() -> Self {
|
async fn get() -> Self {
|
||||||
let names = ["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"];
|
let names = ["ALICE", "BOB", "CHARLIE", "DAVID", "FRANK"];
|
||||||
let mut uds = vec![];
|
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)]
|
#[cfg(test)]
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ pub struct AnnouncedUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UserDiscoveryStore {
|
pub trait UserDiscoveryStore {
|
||||||
fn new() -> impl std::future::Future<Output = Self> + Send;
|
|
||||||
fn get_config(&self) -> impl Future<Output = Result<String>> + Send;
|
fn get_config(&self) -> impl Future<Output = Result<String>> + Send;
|
||||||
fn update_config(&self, update: String) -> impl Future<Output = Result<()>> + 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;
|
fn set_shares(&self, shares: Vec<Vec<u8>>) -> impl Future<Output = Result<()>> + Send;
|
||||||
|
|
@ -88,6 +87,9 @@ pub trait UserDiscoveryUtils {
|
||||||
pubkey: &[u8],
|
pubkey: &[u8],
|
||||||
signature: &[u8],
|
signature: &[u8],
|
||||||
) -> impl Future<Output = Result<bool>> + Send;
|
) -> 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(
|
fn verify_stored_pubkey(
|
||||||
&self,
|
&self,
|
||||||
from_contact_id: UserID,
|
from_contact_id: UserID,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue