mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 07:24:07 +00:00
commit
d19f4ce3c9
139 changed files with 42166 additions and 2690 deletions
|
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
## 0.5.0
|
||||
|
||||
- New: Update to Signal's new PQC-ready key agreement PQXDH
|
||||
- New: Contact labels
|
||||
- Fix: Multiple bug fixes
|
||||
- Fix: Multiple black screens
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- New: Encrypted Cloud Backup of Memories
|
||||
|
|
|
|||
21
lib/app.dart
21
lib/app.dart
|
|
@ -89,15 +89,17 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
Locale('de', ''),
|
||||
];
|
||||
|
||||
final settings = context.watch<SettingsChangeProvider>();
|
||||
|
||||
if (widget.storageError) {
|
||||
return MaterialApp(
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
home: const CriticalErrorView(),
|
||||
);
|
||||
}
|
||||
|
|
@ -108,9 +110,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
home: const RecoveryView(),
|
||||
);
|
||||
}
|
||||
|
|
@ -121,9 +123,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -161,6 +163,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
initAsync();
|
||||
|
||||
void handleShareLink(Uri uri) {
|
||||
HomeViewState.pendingSharedLink = uri;
|
||||
routerProvider.go(Routes.home);
|
||||
HomeViewState.streamHomeViewPageIndex.add(1);
|
||||
HomeViewState.streamSharedLink.add(uri);
|
||||
|
|
|
|||
57
lib/core/bridge/wrapper/signal.dart
Normal file
57
lib/core/bridge/wrapper/signal.dart
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../../frb_generated.dart';
|
||||
import '../../signal/engine.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
class RustSignal {
|
||||
const RustSignal();
|
||||
|
||||
static Future<Uint8List> decrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> ciphertext,
|
||||
}) => RustLib.instance.api.crateBridgeWrapperSignalRustSignalDecrypt(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
ciphertext: ciphertext,
|
||||
);
|
||||
|
||||
static Future<Uint8List> encrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> plaintext,
|
||||
}) => RustLib.instance.api.crateBridgeWrapperSignalRustSignalEncrypt(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
plaintext: plaintext,
|
||||
);
|
||||
|
||||
static Future<FrbPreKeyBundle> generateBundle() =>
|
||||
RustLib.instance.api.crateBridgeWrapperSignalRustSignalGenerateBundle();
|
||||
|
||||
static Future<List<FrbPqcPreKey>> generatePqcPrekeys() => RustLib.instance.api
|
||||
.crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys();
|
||||
|
||||
static Future<void> processPrekeyBundle({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required FrbPreKeyBundle bundle,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperSignalRustSignalProcessPrekeyBundle(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
bundle: bundle,
|
||||
);
|
||||
|
||||
@override
|
||||
int get hashCode => 0;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RustSignal && runtimeType == other.runtimeType;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import 'bridge/callbacks/user_discovery.dart';
|
|||
import 'bridge/wrapper.dart';
|
||||
import 'bridge/wrapper/backup.dart';
|
||||
import 'bridge/wrapper/key_manager.dart';
|
||||
import 'bridge/wrapper/signal.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -18,6 +19,7 @@ import 'frb_generated.io.dart'
|
|||
import 'keys/backup_password_keys.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'signal/engine.dart';
|
||||
|
||||
/// Main entrypoint of the Rust API
|
||||
class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
|
|
@ -76,7 +78,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
String get codegenVersion => '2.12.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 1788847092;
|
||||
int get rustContentHash => 1109927244;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
|
|
@ -259,6 +261,29 @@ abstract class RustLibApi extends BaseApi {
|
|||
required List<int> record,
|
||||
});
|
||||
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalDecrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> ciphertext,
|
||||
});
|
||||
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalEncrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> plaintext,
|
||||
});
|
||||
|
||||
Future<FrbPreKeyBundle> crateBridgeWrapperSignalRustSignalGenerateBundle();
|
||||
|
||||
Future<List<FrbPqcPreKey>>
|
||||
crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys();
|
||||
|
||||
Future<void> crateBridgeWrapperSignalRustSignalProcessPrekeyBundle({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required FrbPreKeyBundle bundle,
|
||||
});
|
||||
|
||||
Future<List<Uint8List>> crateBridgeWrapperRustUtilsGenerateShares({
|
||||
required List<int> secret,
|
||||
required int total,
|
||||
|
|
@ -1651,6 +1676,183 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
argNames: ["signedPreKeyId", "record"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalDecrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> ciphertext,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
sse_encode_u_32(deviceId, serializer);
|
||||
sse_encode_list_prim_u_8_loose(ciphertext, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 32,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeWrapperSignalRustSignalDecryptConstMeta,
|
||||
argValues: [name, deviceId, ciphertext],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateBridgeWrapperSignalRustSignalDecryptConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_decrypt",
|
||||
argNames: ["name", "deviceId", "ciphertext"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalEncrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> plaintext,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
sse_encode_u_32(deviceId, serializer);
|
||||
sse_encode_list_prim_u_8_loose(plaintext, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 33,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeWrapperSignalRustSignalEncryptConstMeta,
|
||||
argValues: [name, deviceId, plaintext],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateBridgeWrapperSignalRustSignalEncryptConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_encrypt",
|
||||
argNames: ["name", "deviceId", "plaintext"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<FrbPreKeyBundle> crateBridgeWrapperSignalRustSignalGenerateBundle() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 34,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_frb_pre_key_bundle,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeWrapperSignalRustSignalGenerateBundleConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalGenerateBundleConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_generate_bundle",
|
||||
argNames: [],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<FrbPqcPreKey>>
|
||||
crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 35,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_frb_pqc_pre_key,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperSignalRustSignalGeneratePqcPrekeysConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalGeneratePqcPrekeysConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_generate_pqc_prekeys",
|
||||
argNames: [],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> crateBridgeWrapperSignalRustSignalProcessPrekeyBundle({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required FrbPreKeyBundle bundle,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
sse_encode_u_32(deviceId, serializer);
|
||||
sse_encode_box_autoadd_frb_pre_key_bundle(bundle, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 36,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_unit,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperSignalRustSignalProcessPrekeyBundleConstMeta,
|
||||
argValues: [name, deviceId, bundle],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalProcessPrekeyBundleConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_process_prekey_bundle",
|
||||
argNames: ["name", "deviceId", "bundle"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<Uint8List>> crateBridgeWrapperRustUtilsGenerateShares({
|
||||
required List<int> secret,
|
||||
|
|
@ -1667,7 +1869,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 32,
|
||||
funcId: 37,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1702,7 +1904,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 33,
|
||||
funcId: 38,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1738,7 +1940,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 34,
|
||||
funcId: 39,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1775,7 +1977,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 35,
|
||||
funcId: 40,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1813,7 +2015,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 36,
|
||||
funcId: 41,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1851,7 +2053,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 37,
|
||||
funcId: 42,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1889,7 +2091,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 38,
|
||||
funcId: 43,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1928,7 +2130,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 39,
|
||||
funcId: 44,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1967,7 +2169,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 40,
|
||||
funcId: 45,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2012,7 +2214,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 41,
|
||||
funcId: 46,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2064,7 +2266,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 42,
|
||||
funcId: 47,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2105,7 +2307,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 43,
|
||||
funcId: 48,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2143,7 +2345,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 44,
|
||||
funcId: 49,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2181,7 +2383,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 45,
|
||||
funcId: 50,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2219,7 +2421,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 46,
|
||||
funcId: 51,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2257,7 +2459,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 47,
|
||||
funcId: 52,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2299,7 +2501,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 48,
|
||||
funcId: 53,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2339,7 +2541,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 49,
|
||||
funcId: 54,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2978,6 +3180,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return dco_decode_backup_password_keys(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dco_decode_frb_pre_key_bundle(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -2996,6 +3204,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return dco_decode_other_promotion(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as int;
|
||||
}
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
|
||||
dynamic raw,
|
||||
|
|
@ -3021,6 +3235,42 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return FlutterUserDiscovery();
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey dco_decode_frb_pqc_pre_key(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 5)
|
||||
throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
|
||||
return FrbPqcPreKey(
|
||||
eccPreKeyId: dco_decode_u_32(arr[0]),
|
||||
eccPreKey: dco_decode_list_prim_u_8_strict(arr[1]),
|
||||
kyberPreKeyId: dco_decode_u_32(arr[2]),
|
||||
kyberPreKey: dco_decode_list_prim_u_8_strict(arr[3]),
|
||||
kyberPreKeySignature: dco_decode_list_prim_u_8_strict(arr[4]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 11)
|
||||
throw Exception('unexpected arr length: expect 11 but see ${arr.length}');
|
||||
return FrbPreKeyBundle(
|
||||
registrationId: dco_decode_u_32(arr[0]),
|
||||
deviceId: dco_decode_u_32(arr[1]),
|
||||
preKeyId: dco_decode_opt_box_autoadd_u_32(arr[2]),
|
||||
preKeyPublic: dco_decode_opt_list_prim_u_8_strict(arr[3]),
|
||||
signedPreKeyId: dco_decode_u_32(arr[4]),
|
||||
signedPreKeyPublic: dco_decode_list_prim_u_8_strict(arr[5]),
|
||||
signedPreKeySignature: dco_decode_list_prim_u_8_strict(arr[6]),
|
||||
kyberPreKeyId: dco_decode_u_32(arr[7]),
|
||||
kyberPreKeyPublic: dco_decode_list_prim_u_8_strict(arr[8]),
|
||||
kyberPreKeySignature: dco_decode_list_prim_u_8_strict(arr[9]),
|
||||
identityKey: dco_decode_list_prim_u_8_strict(arr[10]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3045,6 +3295,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return dcoDecodeI64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return (raw as List<dynamic>).map(dco_decode_frb_pqc_pre_key).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3096,6 +3352,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return raw == null ? null : dco_decode_box_autoadd_i_64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw == null ? null : dco_decode_box_autoadd_u_32(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3200,6 +3462,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return RustKeyManager();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustSignal dco_decode_rust_signal(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 0)
|
||||
throw Exception('unexpected arr length: expect 0 but see ${arr.length}');
|
||||
return RustSignal();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustUtils dco_decode_rust_utils(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3347,6 +3618,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return (sse_decode_backup_password_keys(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_box_autoadd_frb_pre_key_bundle(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_frb_pre_key_bundle(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -3367,6 +3646,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return (sse_decode_other_promotion(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_u_32(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -3391,6 +3676,58 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return FlutterUserDiscovery();
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey sse_decode_frb_pqc_pre_key(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_eccPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_eccPreKey = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_kyberPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_kyberPreKey = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_kyberPreKeySignature = sse_decode_list_prim_u_8_strict(
|
||||
deserializer,
|
||||
);
|
||||
return FrbPqcPreKey(
|
||||
eccPreKeyId: var_eccPreKeyId,
|
||||
eccPreKey: var_eccPreKey,
|
||||
kyberPreKeyId: var_kyberPreKeyId,
|
||||
kyberPreKey: var_kyberPreKey,
|
||||
kyberPreKeySignature: var_kyberPreKeySignature,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_registrationId = sse_decode_u_32(deserializer);
|
||||
var var_deviceId = sse_decode_u_32(deserializer);
|
||||
var var_preKeyId = sse_decode_opt_box_autoadd_u_32(deserializer);
|
||||
var var_preKeyPublic = sse_decode_opt_list_prim_u_8_strict(deserializer);
|
||||
var var_signedPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_signedPreKeyPublic = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_signedPreKeySignature = sse_decode_list_prim_u_8_strict(
|
||||
deserializer,
|
||||
);
|
||||
var var_kyberPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_kyberPreKeyPublic = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_kyberPreKeySignature = sse_decode_list_prim_u_8_strict(
|
||||
deserializer,
|
||||
);
|
||||
var var_identityKey = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
return FrbPreKeyBundle(
|
||||
registrationId: var_registrationId,
|
||||
deviceId: var_deviceId,
|
||||
preKeyId: var_preKeyId,
|
||||
preKeyPublic: var_preKeyPublic,
|
||||
signedPreKeyId: var_signedPreKeyId,
|
||||
signedPreKeyPublic: var_signedPreKeyPublic,
|
||||
signedPreKeySignature: var_signedPreKeySignature,
|
||||
kyberPreKeyId: var_kyberPreKeyId,
|
||||
kyberPreKeyPublic: var_kyberPreKeyPublic,
|
||||
kyberPreKeySignature: var_kyberPreKeySignature,
|
||||
identityKey: var_identityKey,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -3411,6 +3748,20 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return deserializer.buffer.getPlatformInt64();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> sse_decode_list_frb_pqc_pre_key(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
var ans_ = <FrbPqcPreKey>[];
|
||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||
ans_.add(sse_decode_frb_pqc_pre_key(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -3503,6 +3854,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
if (sse_decode_bool(deserializer)) {
|
||||
return (sse_decode_box_autoadd_u_32(deserializer));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -3613,6 +3975,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return RustKeyManager();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustSignal sse_decode_rust_signal(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return RustSignal();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustUtils sse_decode_rust_utils(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -3946,6 +4314,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
sse_encode_backup_password_keys(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_frb_pre_key_bundle(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -3973,6 +4350,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
sse_encode_other_promotion(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_32(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_user_discovery_store_flutter(
|
||||
UserDiscoveryStoreFlutter self,
|
||||
|
|
@ -3999,6 +4382,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pqc_pre_key(FrbPqcPreKey self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_32(self.eccPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.eccPreKey, serializer);
|
||||
sse_encode_u_32(self.kyberPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKey, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_32(self.registrationId, serializer);
|
||||
sse_encode_u_32(self.deviceId, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(self.preKeyId, serializer);
|
||||
sse_encode_opt_list_prim_u_8_strict(self.preKeyPublic, serializer);
|
||||
sse_encode_u_32(self.signedPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.signedPreKeyPublic, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.signedPreKeySignature, serializer);
|
||||
sse_encode_u_32(self.kyberPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKeyPublic, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.identityKey, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -4018,6 +4430,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
serializer.buffer.putPlatformInt64(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_frb_pqc_pre_key(
|
||||
List<FrbPqcPreKey> self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
for (final item in self) {
|
||||
sse_encode_frb_pqc_pre_key(item, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
|
|
@ -4112,6 +4536,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
sse_encode_bool(self != null, serializer);
|
||||
if (self != null) {
|
||||
sse_encode_box_autoadd_u_32(self, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
|
|
@ -4222,6 +4656,11 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_signal(RustSignal self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'bridge/callbacks/user_discovery.dart';
|
|||
import 'bridge/wrapper.dart';
|
||||
import 'bridge/wrapper/backup.dart';
|
||||
import 'bridge/wrapper/key_manager.dart';
|
||||
import 'bridge/wrapper/signal.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -17,6 +18,7 @@ import 'frb_generated.dart';
|
|||
import 'keys/backup_password_keys.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||
import 'signal/engine.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
|
|
@ -130,6 +132,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -139,6 +144,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
|
||||
dynamic raw,
|
||||
|
|
@ -152,6 +160,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey dco_decode_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -161,6 +175,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -186,6 +203,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -220,6 +240,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustSignal dco_decode_rust_signal(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustUtils dco_decode_rust_utils(dynamic raw);
|
||||
|
||||
|
|
@ -288,6 +311,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_box_autoadd_frb_pre_key_bundle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -299,6 +327,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -314,6 +345,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey sse_decode_frb_pqc_pre_key(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -323,6 +360,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> sse_decode_list_frb_pqc_pre_key(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -356,6 +398,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -400,6 +445,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustSignal sse_decode_rust_signal(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -561,6 +609,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -579,6 +633,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_user_discovery_store_flutter(
|
||||
UserDiscoveryStoreFlutter self,
|
||||
|
|
@ -597,6 +654,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pqc_pre_key(FrbPqcPreKey self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
|
|
@ -606,6 +672,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_frb_pqc_pre_key(
|
||||
List<FrbPqcPreKey> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
|
|
@ -648,6 +720,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
|
|
@ -708,6 +783,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_signal(RustSignal self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'bridge/callbacks/user_discovery.dart';
|
|||
import 'bridge/wrapper.dart';
|
||||
import 'bridge/wrapper/backup.dart';
|
||||
import 'bridge/wrapper/key_manager.dart';
|
||||
import 'bridge/wrapper/signal.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -19,6 +20,7 @@ import 'frb_generated.dart';
|
|||
import 'keys/backup_password_keys.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
||||
import 'signal/engine.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
|
|
@ -132,6 +134,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -141,6 +146,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
|
||||
dynamic raw,
|
||||
|
|
@ -154,6 +162,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey dco_decode_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -163,6 +177,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -188,6 +205,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -222,6 +242,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustSignal dco_decode_rust_signal(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustUtils dco_decode_rust_utils(dynamic raw);
|
||||
|
||||
|
|
@ -290,6 +313,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_box_autoadd_frb_pre_key_bundle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -301,6 +329,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -316,6 +347,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey sse_decode_frb_pqc_pre_key(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -325,6 +362,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> sse_decode_list_frb_pqc_pre_key(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -358,6 +400,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -402,6 +447,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustSignal sse_decode_rust_signal(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -563,6 +611,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -581,6 +635,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_user_discovery_store_flutter(
|
||||
UserDiscoveryStoreFlutter self,
|
||||
|
|
@ -599,6 +656,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pqc_pre_key(FrbPqcPreKey self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
|
|
@ -608,6 +674,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_frb_pqc_pre_key(
|
||||
List<FrbPqcPreKey> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
|
|
@ -650,6 +722,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
|
|
@ -710,6 +785,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_signal(RustSignal self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
|
||||
|
||||
|
|
|
|||
101
lib/core/signal/engine.dart
Normal file
101
lib/core/signal/engine.dart
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../frb_generated.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
class FrbPqcPreKey {
|
||||
final int eccPreKeyId;
|
||||
final Uint8List eccPreKey;
|
||||
final int kyberPreKeyId;
|
||||
final Uint8List kyberPreKey;
|
||||
final Uint8List kyberPreKeySignature;
|
||||
|
||||
const FrbPqcPreKey({
|
||||
required this.eccPreKeyId,
|
||||
required this.eccPreKey,
|
||||
required this.kyberPreKeyId,
|
||||
required this.kyberPreKey,
|
||||
required this.kyberPreKeySignature,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
eccPreKeyId.hashCode ^
|
||||
eccPreKey.hashCode ^
|
||||
kyberPreKeyId.hashCode ^
|
||||
kyberPreKey.hashCode ^
|
||||
kyberPreKeySignature.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FrbPqcPreKey &&
|
||||
runtimeType == other.runtimeType &&
|
||||
eccPreKeyId == other.eccPreKeyId &&
|
||||
eccPreKey == other.eccPreKey &&
|
||||
kyberPreKeyId == other.kyberPreKeyId &&
|
||||
kyberPreKey == other.kyberPreKey &&
|
||||
kyberPreKeySignature == other.kyberPreKeySignature;
|
||||
}
|
||||
|
||||
class FrbPreKeyBundle {
|
||||
final int registrationId;
|
||||
final int deviceId;
|
||||
final int? preKeyId;
|
||||
final Uint8List? preKeyPublic;
|
||||
final int signedPreKeyId;
|
||||
final Uint8List signedPreKeyPublic;
|
||||
final Uint8List signedPreKeySignature;
|
||||
final int kyberPreKeyId;
|
||||
final Uint8List kyberPreKeyPublic;
|
||||
final Uint8List kyberPreKeySignature;
|
||||
final Uint8List identityKey;
|
||||
|
||||
const FrbPreKeyBundle({
|
||||
required this.registrationId,
|
||||
required this.deviceId,
|
||||
this.preKeyId,
|
||||
this.preKeyPublic,
|
||||
required this.signedPreKeyId,
|
||||
required this.signedPreKeyPublic,
|
||||
required this.signedPreKeySignature,
|
||||
required this.kyberPreKeyId,
|
||||
required this.kyberPreKeyPublic,
|
||||
required this.kyberPreKeySignature,
|
||||
required this.identityKey,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
registrationId.hashCode ^
|
||||
deviceId.hashCode ^
|
||||
preKeyId.hashCode ^
|
||||
preKeyPublic.hashCode ^
|
||||
signedPreKeyId.hashCode ^
|
||||
signedPreKeyPublic.hashCode ^
|
||||
signedPreKeySignature.hashCode ^
|
||||
kyberPreKeyId.hashCode ^
|
||||
kyberPreKeyPublic.hashCode ^
|
||||
kyberPreKeySignature.hashCode ^
|
||||
identityKey.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FrbPreKeyBundle &&
|
||||
runtimeType == other.runtimeType &&
|
||||
registrationId == other.registrationId &&
|
||||
deviceId == other.deviceId &&
|
||||
preKeyId == other.preKeyId &&
|
||||
preKeyPublic == other.preKeyPublic &&
|
||||
signedPreKeyId == other.signedPreKeyId &&
|
||||
signedPreKeyPublic == other.signedPreKeyPublic &&
|
||||
signedPreKeySignature == other.signedPreKeySignature &&
|
||||
kyberPreKeyId == other.kyberPreKeyId &&
|
||||
kyberPreKeyPublic == other.kyberPreKeyPublic &&
|
||||
kyberPreKeySignature == other.kyberPreKeySignature &&
|
||||
identityKey == other.identityKey;
|
||||
}
|
||||
|
|
@ -175,13 +175,15 @@ Future<void> postStartupTasks() async {
|
|||
// 2. Service initializations
|
||||
unawaited(finishStartedPreprocessing());
|
||||
unawaited(createPushAvatars());
|
||||
unawaited(newsService.init().then((_) {
|
||||
unawaited(
|
||||
newsService.init().then((_) {
|
||||
final lastDownload = newsService.lastDownloadedAt;
|
||||
if (lastDownload == null ||
|
||||
DateTime.now().difference(lastDownload) >= const Duration(days: 7)) {
|
||||
newsService.fetchFeed();
|
||||
}
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
unawaited(UserDiscoveryService.verifyInitializationOnStartup());
|
||||
|
||||
|
|
|
|||
|
|
@ -340,6 +340,24 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
.write(GroupsCompanion(lastMessageExchange: Value(clampedLastMessage)));
|
||||
}
|
||||
|
||||
Future<void> increaseMemberLastMessage(
|
||||
String groupId,
|
||||
int contactId,
|
||||
DateTime newLastMessage,
|
||||
) async {
|
||||
final now = clock.now();
|
||||
final clampedLastMessage =
|
||||
newLastMessage.isAfter(now) ? now : newLastMessage;
|
||||
await (update(groupMembers)..where(
|
||||
(t) =>
|
||||
t.groupId.equals(groupId) &
|
||||
t.contactId.equals(contactId) &
|
||||
(t.lastMessage.isNull() |
|
||||
t.lastMessage.isSmallerThanValue(clampedLastMessage)),
|
||||
))
|
||||
.write(GroupMembersCompanion(lastMessage: Value(clampedLastMessage)));
|
||||
}
|
||||
|
||||
Stream<List<Group>> watchNonDirectGroupsForMember(int contactId) {
|
||||
final query =
|
||||
select(groups).join([
|
||||
|
|
|
|||
91
lib/src/database/daos/labels.dao.dart
Normal file
91
lib/src/database/daos/labels.dao.dart
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/src/database/tables/labels.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
||||
part 'labels.dao.g.dart';
|
||||
|
||||
@DriftAccessor(
|
||||
tables: [
|
||||
Labels,
|
||||
ContactLabels,
|
||||
],
|
||||
)
|
||||
class LabelsDao extends DatabaseAccessor<TwonlyDB> with _$LabelsDaoMixin {
|
||||
LabelsDao(super.db);
|
||||
|
||||
Stream<List<Label>> watchAllLabels() {
|
||||
return (select(labels)..orderBy([(t) => OrderingTerm(expression: t.name)])).watch();
|
||||
}
|
||||
|
||||
Future<List<Label>> getAllLabels() {
|
||||
return (select(labels)..orderBy([(t) => OrderingTerm(expression: t.name)])).get();
|
||||
}
|
||||
|
||||
Stream<List<Label>> watchContactLabels(int contactId) {
|
||||
final query = select(contactLabels).join([
|
||||
innerJoin(labels, labels.id.equalsExp(contactLabels.labelId)),
|
||||
])..where(contactLabels.contactId.equals(contactId));
|
||||
|
||||
return query.watch().map(
|
||||
(rows) => rows.map((row) => row.readTable(labels)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Label>> getContactLabels(int contactId) {
|
||||
final query = select(contactLabels).join([
|
||||
innerJoin(labels, labels.id.equalsExp(contactLabels.labelId)),
|
||||
])..where(contactLabels.contactId.equals(contactId));
|
||||
|
||||
return query.get().then(
|
||||
(rows) => rows.map((row) => row.readTable(labels)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setContactLabels(int contactId, List<int> labelIds) async {
|
||||
final sanitizedLabelIds = labelIds.take(3).toList();
|
||||
await transaction(() async {
|
||||
await (delete(contactLabels)..where((t) => t.contactId.equals(contactId))).go();
|
||||
if (sanitizedLabelIds.isNotEmpty) {
|
||||
await batch((b) {
|
||||
b.insertAll(
|
||||
contactLabels,
|
||||
sanitizedLabelIds.map(
|
||||
(lId) => ContactLabelsCompanion.insert(
|
||||
contactId: contactId,
|
||||
labelId: lId,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> createLabel(String name, int textColor, int backgroundColor) {
|
||||
final sanitizedName = name.length > 8 ? name.substring(0, 8) : name;
|
||||
return into(labels).insert(
|
||||
LabelsCompanion.insert(
|
||||
name: sanitizedName,
|
||||
textColor: textColor,
|
||||
backgroundColor: backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> updateLabel(int id, String name, int textColor, int backgroundColor) {
|
||||
final sanitizedName = name.length > 8 ? name.substring(0, 8) : name;
|
||||
return (update(labels)..where((t) => t.id.equals(id)))
|
||||
.write(
|
||||
LabelsCompanion(
|
||||
name: Value(sanitizedName),
|
||||
textColor: Value(textColor),
|
||||
backgroundColor: Value(backgroundColor),
|
||||
),
|
||||
)
|
||||
.then((rows) => rows > 0);
|
||||
}
|
||||
|
||||
Future<int> deleteLabel(int id) {
|
||||
return (delete(labels)..where((t) => t.id.equals(id))).go();
|
||||
}
|
||||
}
|
||||
22
lib/src/database/daos/labels.dao.g.dart
Normal file
22
lib/src/database/daos/labels.dao.g.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'labels.dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$LabelsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||
$LabelsTable get labels => attachedDatabase.labels;
|
||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
||||
$ContactLabelsTable get contactLabels => attachedDatabase.contactLabels;
|
||||
LabelsDaoManager get managers => LabelsDaoManager(this);
|
||||
}
|
||||
|
||||
class LabelsDaoManager {
|
||||
final _$LabelsDaoMixin _db;
|
||||
LabelsDaoManager(this._db);
|
||||
$$LabelsTableTableManager get labels =>
|
||||
$$LabelsTableTableManager(_db.attachedDatabase, _db.labels);
|
||||
$$ContactsTableTableManager get contacts =>
|
||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||
$$ContactLabelsTableTableManager get contactLabels =>
|
||||
$$ContactLabelsTableTableManager(_db.attachedDatabase, _db.contactLabels);
|
||||
}
|
||||
|
|
@ -489,22 +489,28 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
|
||||
await into(messages).insertOnConflictUpdate(insertMessage);
|
||||
|
||||
final msgTime = insertMessage.createdAt.present
|
||||
? insertMessage.createdAt.value
|
||||
: clock.now();
|
||||
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
message.groupId.value,
|
||||
GroupsCompanion(
|
||||
lastMessageExchange: Value(clock.now()),
|
||||
archived: const Value(false),
|
||||
deletedContent: const Value(false),
|
||||
const GroupsCompanion(
|
||||
archived: Value(false),
|
||||
deletedContent: Value(false),
|
||||
),
|
||||
);
|
||||
|
||||
if (message.senderId.present) {
|
||||
await twonlyDB.groupsDao.updateMember(
|
||||
await twonlyDB.groupsDao.increaseLastMessageExchange(
|
||||
message.groupId.value,
|
||||
msgTime,
|
||||
);
|
||||
|
||||
if (message.senderId.present && message.senderId.value != null) {
|
||||
await twonlyDB.groupsDao.increaseMemberLastMessage(
|
||||
message.groupId.value,
|
||||
message.senderId.value!,
|
||||
GroupMembersCompanion(
|
||||
lastMessage: Value(clock.now()),
|
||||
),
|
||||
msgTime,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
3322
lib/src/database/schemas/twonly_db/drift_schema_v24.json
Normal file
3322
lib/src/database/schemas/twonly_db/drift_schema_v24.json
Normal file
File diff suppressed because it is too large
Load diff
3091
lib/src/database/schemas/twonly_db/drift_schema_v25.json
Normal file
3091
lib/src/database/schemas/twonly_db/drift_schema_v25.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -63,6 +63,7 @@ class SignalSignedPreKeyStore extends SignedPreKeyStore {
|
|||
.insert(companion, mode: InsertMode.insertOrReplace);
|
||||
} catch (e) {
|
||||
Log.error('$e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:drift/drift.dart';
|
||||
|
||||
|
||||
enum SignalVersion { v1, v2 }
|
||||
|
||||
@DataClassName('Contact')
|
||||
class Contacts extends Table {
|
||||
IntColumn get userId => integer()();
|
||||
|
|
@ -23,6 +26,9 @@ class Contacts extends Table {
|
|||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
TextColumn get signalVersion =>
|
||||
textEnum<SignalVersion>().withDefault(const Constant('v1'))();
|
||||
|
||||
// User Discovery
|
||||
BlobColumn get userDiscoveryVersion => blob().nullable()();
|
||||
BoolColumn get userDiscoveryExcluded =>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Groups extends Table {
|
|||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get deleteMessagesAfterMilliseconds => integer().withDefault(
|
||||
const Constant(defaultDeleteMessagesAfterMilliseconds),
|
||||
const Constant(1000 * 60 * 60 * 24),
|
||||
)();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
|
|
|||
28
lib/src/database/tables/labels.table.dart
Normal file
28
lib/src/database/tables/labels.table.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
|
||||
@DataClassName('Label')
|
||||
class Labels extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text()();
|
||||
IntColumn get textColor => integer()();
|
||||
IntColumn get backgroundColor => integer()();
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
@DataClassName('ContactLabel')
|
||||
class ContactLabels extends Table {
|
||||
IntColumn get contactId => integer().references(
|
||||
Contacts,
|
||||
#userId,
|
||||
onDelete: KeyAction.cascade,
|
||||
)();
|
||||
IntColumn get labelId => integer().references(
|
||||
Labels,
|
||||
#id,
|
||||
onDelete: KeyAction.cascade,
|
||||
)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {contactId, labelId};
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/daos/groups.dao.dart';
|
||||
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||||
import 'package:twonly/src/database/daos/labels.dao.dart';
|
||||
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||
import 'package:twonly/src/database/daos/messages.dao.dart';
|
||||
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
||||
|
|
@ -15,6 +16,7 @@ import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
|||
import 'package:twonly/src/database/drift_logging_interceptor.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/tables/labels.table.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/tables/reactions.table.dart';
|
||||
|
|
@ -59,6 +61,8 @@ part 'twonly.db.g.dart';
|
|||
UserDiscoveryShares,
|
||||
Shortcuts,
|
||||
ShortcutMembers,
|
||||
Labels,
|
||||
ContactLabels,
|
||||
],
|
||||
daos: [
|
||||
MessagesDao,
|
||||
|
|
@ -70,6 +74,7 @@ part 'twonly.db.g.dart';
|
|||
UserDiscoveryDao,
|
||||
KeyVerificationDao,
|
||||
ShortcutsDao,
|
||||
LabelsDao,
|
||||
],
|
||||
)
|
||||
class TwonlyDB extends _$TwonlyDB {
|
||||
|
|
@ -82,7 +87,7 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 23;
|
||||
int get schemaVersion => 25;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
final connection = driftDatabase(
|
||||
|
|
@ -285,6 +290,13 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
schema.mediaFiles.blurhash,
|
||||
);
|
||||
},
|
||||
from23To24: (m, schema) async {
|
||||
await m.createTable(schema.labels);
|
||||
await m.createTable(schema.contactLabels);
|
||||
},
|
||||
from24To25: (m, schema) async {
|
||||
await m.addColumn(schema.contacts, schema.contacts.signalVersion);
|
||||
},
|
||||
)(m, from, to);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1424,6 +1424,12 @@ abstract class AppLocalizations {
|
|||
/// **'Delete for me'**
|
||||
String get deleteOkBtnForMe;
|
||||
|
||||
/// No description provided for @deleteOnlyForMe.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete only for me'**
|
||||
String get deleteOnlyForMe;
|
||||
|
||||
/// No description provided for @deleteImageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1541,7 +1547,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @backupArchiveHeader.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contacts, Settings and Messages'**
|
||||
/// **'Contacts & Messages'**
|
||||
String get backupArchiveHeader;
|
||||
|
||||
/// No description provided for @backupLastBackupDate.
|
||||
|
|
@ -1595,7 +1601,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @backupSelectStrongPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose a secure password. This is required if you want to restore your twonly Backup.'**
|
||||
/// **'Choose a secure password. This is required if you want to restore your backup.'**
|
||||
String get backupSelectStrongPassword;
|
||||
|
||||
/// No description provided for @password.
|
||||
|
|
@ -1652,6 +1658,36 @@ abstract class AppLocalizations {
|
|||
/// **'You can only change your password after you have authenticated!'**
|
||||
String get backupChangePasswordAuthFailed;
|
||||
|
||||
/// No description provided for @backupFreeSpaceWithCloud.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Free Space with Cloud Backup'**
|
||||
String get backupFreeSpaceWithCloud;
|
||||
|
||||
/// No description provided for @backupMemoriesNotEnabled.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not enabled'**
|
||||
String get backupMemoriesNotEnabled;
|
||||
|
||||
/// No description provided for @backupMemoriesUpgradeRequired.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Upgrade required'**
|
||||
String get backupMemoriesUpgradeRequired;
|
||||
|
||||
/// No description provided for @todayAt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Today at {time}'**
|
||||
String todayAt(Object time);
|
||||
|
||||
/// No description provided for @yesterdayAt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yesterday at {time}'**
|
||||
String yesterdayAt(Object time);
|
||||
|
||||
/// No description provided for @twonlySafeRecoverTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3404,6 +3440,12 @@ abstract class AppLocalizations {
|
|||
/// **'Zero ads. Total privacy.'**
|
||||
String get subscriptionPledgeSubtitle;
|
||||
|
||||
/// No description provided for @subscriptionManage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Manage subscription'**
|
||||
String get subscriptionManage;
|
||||
|
||||
/// No description provided for @dragToZoom.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3671,7 +3713,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecovery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Passwordless Recovery'**
|
||||
/// **'Password Recovery'**
|
||||
String get passwordlessRecovery;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNotConfigured.
|
||||
|
|
@ -3917,7 +3959,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecoveryTrustedFriends.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Trusted Friends'**
|
||||
/// **'Account recovery'**
|
||||
String get passwordlessRecoveryTrustedFriends;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryDoneBtn.
|
||||
|
|
@ -4097,7 +4139,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @missingRecoveryContactsCardTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recovery Contacts'**
|
||||
/// **'Account Recovery'**
|
||||
String get missingRecoveryContactsCardTitle;
|
||||
|
||||
/// No description provided for @missingRecoveryContactsCardDesc.
|
||||
|
|
@ -4193,7 +4235,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @settingsStorageContents.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Storage contents'**
|
||||
/// **'Free up space'**
|
||||
String get settingsStorageContents;
|
||||
|
||||
/// No description provided for @settingsStorageSortStorage.
|
||||
|
|
@ -4273,6 +4315,126 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'All memories are up to date.'**
|
||||
String get settingsStorageSyncUpToDate;
|
||||
|
||||
/// No description provided for @restoreLostFlames.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Restore your {count} lost flames'**
|
||||
String restoreLostFlames(int count);
|
||||
|
||||
/// No description provided for @settingsShowRestoreFlameTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show flame restore warning'**
|
||||
String get settingsShowRestoreFlameTitle;
|
||||
|
||||
/// No description provided for @contactLabelsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contact Labels'**
|
||||
String get contactLabelsTitle;
|
||||
|
||||
/// No description provided for @contactLabelsSubtitleEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No labels selected'**
|
||||
String get contactLabelsSubtitleEmpty;
|
||||
|
||||
/// No description provided for @contactLabelsMaxLimit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maximum 3 labels per contact'**
|
||||
String get contactLabelsMaxLimit;
|
||||
|
||||
/// No description provided for @createLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create new label'**
|
||||
String get createLabel;
|
||||
|
||||
/// No description provided for @editLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit label'**
|
||||
String get editLabel;
|
||||
|
||||
/// No description provided for @deleteLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete label'**
|
||||
String get deleteLabel;
|
||||
|
||||
/// No description provided for @deleteLabelConfirmation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete this label? It will be removed from all contacts.'**
|
||||
String get deleteLabelConfirmation;
|
||||
|
||||
/// No description provided for @labelNameHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Label name'**
|
||||
String get labelNameHint;
|
||||
|
||||
/// No description provided for @labelTextColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Text color'**
|
||||
String get labelTextColor;
|
||||
|
||||
/// No description provided for @labelBackgroundColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Background color'**
|
||||
String get labelBackgroundColor;
|
||||
|
||||
/// No description provided for @customColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom Color'**
|
||||
String get customColor;
|
||||
|
||||
/// No description provided for @hue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hue'**
|
||||
String get hue;
|
||||
|
||||
/// No description provided for @saturation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Saturation'**
|
||||
String get saturation;
|
||||
|
||||
/// No description provided for @brightness.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Brightness'**
|
||||
String get brightness;
|
||||
|
||||
/// No description provided for @settingsAppearancePrimaryColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Primary Color'**
|
||||
String get settingsAppearancePrimaryColor;
|
||||
|
||||
/// No description provided for @themeSystemDefault.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'System default'**
|
||||
String get themeSystemDefault;
|
||||
|
||||
/// No description provided for @themeLight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Light'**
|
||||
String get themeLight;
|
||||
|
||||
/// No description provided for @themeDark.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dark'**
|
||||
String get themeDark;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -741,6 +741,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get deleteOkBtnForMe => 'Für mich löschen';
|
||||
|
||||
@override
|
||||
String get deleteOnlyForMe => 'Nur für mich löschen';
|
||||
|
||||
@override
|
||||
String get deleteImageTitle => 'Bist du dir sicher?';
|
||||
|
||||
|
|
@ -817,7 +820,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupIdentityHeader => 'Identität';
|
||||
|
||||
@override
|
||||
String get backupArchiveHeader => 'Kontakte, Einstellungen und Nachrichten';
|
||||
String get backupArchiveHeader => 'Kontakte & Nachrichten';
|
||||
|
||||
@override
|
||||
String get backupLastBackupDate => 'Letztes Backup';
|
||||
|
|
@ -847,7 +850,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get backupSelectStrongPassword =>
|
||||
'Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Backup wiederherstellen möchtest.';
|
||||
'Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein Backup wiederherstellen möchtest.';
|
||||
|
||||
@override
|
||||
String get password => 'Passwort';
|
||||
|
|
@ -878,6 +881,26 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupChangePasswordAuthFailed =>
|
||||
'Du kannst dein Passwort nur ändern, wenn du dich authentifiziert hast!';
|
||||
|
||||
@override
|
||||
String get backupFreeSpaceWithCloud =>
|
||||
'Speicherplatz mit Cloud-Backup freigeben';
|
||||
|
||||
@override
|
||||
String get backupMemoriesNotEnabled => 'Nicht aktiviert';
|
||||
|
||||
@override
|
||||
String get backupMemoriesUpgradeRequired => 'Upgrade erforderlich';
|
||||
|
||||
@override
|
||||
String todayAt(Object time) {
|
||||
return 'Heute um $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String yesterdayAt(Object time) {
|
||||
return 'Gestern um $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
|
||||
|
||||
|
|
@ -1960,6 +1983,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get subscriptionPledgeSubtitle => 'Keine Werbung. Volle Privatsphäre.';
|
||||
|
||||
@override
|
||||
String get subscriptionManage => 'Abonnement verwalten';
|
||||
|
||||
@override
|
||||
String get dragToZoom => 'Zum Zoomen ziehen';
|
||||
|
||||
|
|
@ -2118,7 +2144,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get avatarCustomizeReset => 'Zurücksetzen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecovery => 'Passwortloses Backup';
|
||||
String get passwordlessRecovery => 'Passwort vergessen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNotConfigured => 'Nicht konfiguriert';
|
||||
|
|
@ -2184,7 +2210,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableSuccess =>
|
||||
'Passwortloses Backup erfolgreich aktiviert!';
|
||||
'\"Passwort vergessen\" erfolgreich aktiviert!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnterPin => 'Bitte gib eine PIN ein.';
|
||||
|
|
@ -2194,13 +2220,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Bitte gib eine E-Mail-Adresse ein.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableBtn => 'Passwortloses Backup aktivieren';
|
||||
String get passwordlessRecoveryEnableBtn =>
|
||||
'\"Passwort vergessen\" aktivieren';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryRecoverBtn => 'Passwortlos wiederherstellen';
|
||||
String get passwordlessRecoveryRecoverBtn =>
|
||||
'Mit \"Passwort vergessen\" wiederherstellen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryModifyBtn => 'Passwortloses Backup bearbeiten';
|
||||
String get passwordlessRecoveryModifyBtn =>
|
||||
'\"Passwort vergessen\" bearbeiten';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryStatusEnabled(num count) {
|
||||
|
|
@ -2261,7 +2290,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get passwordlessRecoverySelectFriends => 'Freunde auswählen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryTrustedFriends => 'Vertrauenswürdige Freunde';
|
||||
String get passwordlessRecoveryTrustedFriends => 'Kontowiederherstellung';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoneBtn(num count) {
|
||||
|
|
@ -2375,7 +2404,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get recoverPasswordlessRecoverNowBtn => 'Jetzt wiederherstellen';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardTitle => 'Vertrauenswürdige Kontakte';
|
||||
String get missingRecoveryContactsCardTitle => 'Kontowiederherstellung';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardDesc =>
|
||||
|
|
@ -2426,7 +2455,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get settingsHelpNews => 'Neuigkeiten';
|
||||
|
||||
@override
|
||||
String get settingsStorageContents => 'Speicherinhalte';
|
||||
String get settingsStorageContents => 'Speicherplatz freigeben';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortStorage => 'Belegter Speicher';
|
||||
|
|
@ -2475,4 +2504,68 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get settingsStorageSyncUpToDate =>
|
||||
'Alle Memories sind auf dem neuesten Stand.';
|
||||
|
||||
@override
|
||||
String restoreLostFlames(int count) {
|
||||
return 'Stelle deine $count verlorenen Flammen wieder her';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsShowRestoreFlameTitle =>
|
||||
'Hinweis zur Flammen-Wiederherstellung anzeigen';
|
||||
|
||||
@override
|
||||
String get contactLabelsTitle => 'Kontaktlabels';
|
||||
|
||||
@override
|
||||
String get contactLabelsSubtitleEmpty => 'Keine Labels ausgewählt';
|
||||
|
||||
@override
|
||||
String get contactLabelsMaxLimit => 'Maximal 3 Labels pro Kontakt';
|
||||
|
||||
@override
|
||||
String get createLabel => 'Neues Label erstellen';
|
||||
|
||||
@override
|
||||
String get editLabel => 'Label bearbeiten';
|
||||
|
||||
@override
|
||||
String get deleteLabel => 'Label löschen';
|
||||
|
||||
@override
|
||||
String get deleteLabelConfirmation =>
|
||||
'Möchtest du dieses Label wirklich löschen? Es wird von allen Kontakten entfernt.';
|
||||
|
||||
@override
|
||||
String get labelNameHint => 'Label-Name';
|
||||
|
||||
@override
|
||||
String get labelTextColor => 'Textfarbe';
|
||||
|
||||
@override
|
||||
String get labelBackgroundColor => 'Hintergrundfarbe';
|
||||
|
||||
@override
|
||||
String get customColor => 'Eigene Farbe';
|
||||
|
||||
@override
|
||||
String get hue => 'Farbton';
|
||||
|
||||
@override
|
||||
String get saturation => 'Sättigung';
|
||||
|
||||
@override
|
||||
String get brightness => 'Helligkeit';
|
||||
|
||||
@override
|
||||
String get settingsAppearancePrimaryColor => 'Hauptfarbe';
|
||||
|
||||
@override
|
||||
String get themeSystemDefault => 'Systemstandard';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Hell';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dunkel';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -736,6 +736,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get deleteOkBtnForMe => 'Delete for me';
|
||||
|
||||
@override
|
||||
String get deleteOnlyForMe => 'Delete only for me';
|
||||
|
||||
@override
|
||||
String get deleteImageTitle => 'Are you sure?';
|
||||
|
||||
|
|
@ -812,7 +815,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get backupIdentityHeader => 'Identity';
|
||||
|
||||
@override
|
||||
String get backupArchiveHeader => 'Contacts, Settings and Messages';
|
||||
String get backupArchiveHeader => 'Contacts & Messages';
|
||||
|
||||
@override
|
||||
String get backupLastBackupDate => 'Last backup';
|
||||
|
|
@ -842,7 +845,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get backupSelectStrongPassword =>
|
||||
'Choose a secure password. This is required if you want to restore your twonly Backup.';
|
||||
'Choose a secure password. This is required if you want to restore your backup.';
|
||||
|
||||
@override
|
||||
String get password => 'Password';
|
||||
|
|
@ -873,6 +876,25 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get backupChangePasswordAuthFailed =>
|
||||
'You can only change your password after you have authenticated!';
|
||||
|
||||
@override
|
||||
String get backupFreeSpaceWithCloud => 'Free Space with Cloud Backup';
|
||||
|
||||
@override
|
||||
String get backupMemoriesNotEnabled => 'Not enabled';
|
||||
|
||||
@override
|
||||
String get backupMemoriesUpgradeRequired => 'Upgrade required';
|
||||
|
||||
@override
|
||||
String todayAt(Object time) {
|
||||
return 'Today at $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String yesterdayAt(Object time) {
|
||||
return 'Yesterday at $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Restore backup';
|
||||
|
||||
|
|
@ -1946,6 +1968,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get subscriptionPledgeSubtitle => 'Zero ads. Total privacy.';
|
||||
|
||||
@override
|
||||
String get subscriptionManage => 'Manage subscription';
|
||||
|
||||
@override
|
||||
String get dragToZoom => 'Drag to Zoom';
|
||||
|
||||
|
|
@ -2104,7 +2129,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get avatarCustomizeReset => 'Reset';
|
||||
|
||||
@override
|
||||
String get passwordlessRecovery => 'Passwordless Recovery';
|
||||
String get passwordlessRecovery => 'Password Recovery';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNotConfigured => 'Not configured';
|
||||
|
|
@ -2245,7 +2270,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get passwordlessRecoverySelectFriends => 'Select trusted friends';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryTrustedFriends => 'Trusted Friends';
|
||||
String get passwordlessRecoveryTrustedFriends => 'Account recovery';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoneBtn(num count) {
|
||||
|
|
@ -2354,7 +2379,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get recoverPasswordlessRecoverNowBtn => 'Recover now';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardTitle => 'Recovery Contacts';
|
||||
String get missingRecoveryContactsCardTitle => 'Account Recovery';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardDesc =>
|
||||
|
|
@ -2405,7 +2430,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get settingsHelpNews => 'News';
|
||||
|
||||
@override
|
||||
String get settingsStorageContents => 'Storage contents';
|
||||
String get settingsStorageContents => 'Free up space';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortStorage => 'Occupied storage';
|
||||
|
|
@ -2453,4 +2478,67 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get settingsStorageSyncUpToDate => 'All memories are up to date.';
|
||||
|
||||
@override
|
||||
String restoreLostFlames(int count) {
|
||||
return 'Restore your $count lost flames';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsShowRestoreFlameTitle => 'Show flame restore warning';
|
||||
|
||||
@override
|
||||
String get contactLabelsTitle => 'Contact Labels';
|
||||
|
||||
@override
|
||||
String get contactLabelsSubtitleEmpty => 'No labels selected';
|
||||
|
||||
@override
|
||||
String get contactLabelsMaxLimit => 'Maximum 3 labels per contact';
|
||||
|
||||
@override
|
||||
String get createLabel => 'Create new label';
|
||||
|
||||
@override
|
||||
String get editLabel => 'Edit label';
|
||||
|
||||
@override
|
||||
String get deleteLabel => 'Delete label';
|
||||
|
||||
@override
|
||||
String get deleteLabelConfirmation =>
|
||||
'Are you sure you want to delete this label? It will be removed from all contacts.';
|
||||
|
||||
@override
|
||||
String get labelNameHint => 'Label name';
|
||||
|
||||
@override
|
||||
String get labelTextColor => 'Text color';
|
||||
|
||||
@override
|
||||
String get labelBackgroundColor => 'Background color';
|
||||
|
||||
@override
|
||||
String get customColor => 'Custom Color';
|
||||
|
||||
@override
|
||||
String get hue => 'Hue';
|
||||
|
||||
@override
|
||||
String get saturation => 'Saturation';
|
||||
|
||||
@override
|
||||
String get brightness => 'Brightness';
|
||||
|
||||
@override
|
||||
String get settingsAppearancePrimaryColor => 'Primary Color';
|
||||
|
||||
@override
|
||||
String get themeSystemDefault => 'System default';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Light';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dark';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 34c835db2de08af962b39fb79ec11341b8847926
|
||||
Subproject commit ce51a6d084db162b4dfb7cbba1c0cda6d8c477d5
|
||||
|
|
@ -57,6 +57,12 @@ class UserData {
|
|||
@JsonKey(defaultValue: ThemeMode.system)
|
||||
ThemeMode themeMode = ThemeMode.system;
|
||||
|
||||
int? primaryColorValue;
|
||||
|
||||
Color get primaryColor => primaryColorValue != null
|
||||
? Color(primaryColorValue!)
|
||||
: const Color(0xFF57CC99);
|
||||
|
||||
int? defaultShowTime;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
|
|
@ -93,10 +99,15 @@ class UserData {
|
|||
@JsonKey(defaultValue: true)
|
||||
bool typingIndicators = true;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool showRestoreFlame = true;
|
||||
|
||||
String? myBestFriendGroupId;
|
||||
|
||||
DateTime? signalLastSignedPreKeyUpdated;
|
||||
|
||||
DateTime? signalLastPqcPreKeysUploaded;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool allowErrorTrackingViaSentry = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..themeMode =
|
||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
||||
ThemeMode.system
|
||||
..primaryColorValue = (json['primaryColorValue'] as num?)?.toInt()
|
||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||
..requestedAudioPermission =
|
||||
json['requestedAudioPermission'] as bool? ?? false
|
||||
|
|
@ -60,11 +61,16 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..autoStoreAllSendUnlimitedMediaFiles =
|
||||
json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false
|
||||
..typingIndicators = json['typingIndicators'] as bool? ?? true
|
||||
..showRestoreFlame = json['showRestoreFlame'] as bool? ?? true
|
||||
..myBestFriendGroupId = json['myBestFriendGroupId'] as String?
|
||||
..signalLastSignedPreKeyUpdated =
|
||||
json['signalLastSignedPreKeyUpdated'] == null
|
||||
? null
|
||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||
..signalLastPqcPreKeysUploaded =
|
||||
json['signalLastPqcPreKeysUploaded'] == null
|
||||
? null
|
||||
: DateTime.parse(json['signalLastPqcPreKeysUploaded'] as String)
|
||||
..allowErrorTrackingViaSentry =
|
||||
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
||||
..screenLockEnabled = json['screenLockEnabled'] as bool? ?? false
|
||||
|
|
@ -136,6 +142,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'lastPlanBallance': instance.lastPlanBallance,
|
||||
'additionalUserInvites': instance.additionalUserInvites,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'primaryColorValue': instance.primaryColorValue,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||
'enableDatabaseLogging': instance.enableDatabaseLogging,
|
||||
|
|
@ -151,9 +158,12 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'autoStoreAllSendUnlimitedMediaFiles':
|
||||
instance.autoStoreAllSendUnlimitedMediaFiles,
|
||||
'typingIndicators': instance.typingIndicators,
|
||||
'showRestoreFlame': instance.showRestoreFlame,
|
||||
'myBestFriendGroupId': instance.myBestFriendGroupId,
|
||||
'signalLastSignedPreKeyUpdated': instance.signalLastSignedPreKeyUpdated
|
||||
?.toIso8601String(),
|
||||
'signalLastPqcPreKeysUploaded': instance.signalLastPqcPreKeysUploaded
|
||||
?.toIso8601String(),
|
||||
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
||||
'screenLockEnabled': instance.screenLockEnabled,
|
||||
'isCloudBackupEnabled': instance.isCloudBackupEnabled,
|
||||
|
|
|
|||
|
|
@ -2059,6 +2059,250 @@ class ApplicationData_UpdateSignedPreKey extends $pb.GeneratedMessage {
|
|||
void clearSignedPrekeySignature() => $_clearField(3);
|
||||
}
|
||||
|
||||
class ApplicationData_PqcPreKey extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_PqcPreKey({
|
||||
$fixnum.Int64? eccPreKeyId,
|
||||
$core.List<$core.int>? eccPreKey,
|
||||
$fixnum.Int64? kyberPreKeyId,
|
||||
$core.List<$core.int>? kyberPreKey,
|
||||
$core.List<$core.int>? kyberPreKeySignature,
|
||||
}) {
|
||||
final result = create();
|
||||
if (eccPreKeyId != null) result.eccPreKeyId = eccPreKeyId;
|
||||
if (eccPreKey != null) result.eccPreKey = eccPreKey;
|
||||
if (kyberPreKeyId != null) result.kyberPreKeyId = kyberPreKeyId;
|
||||
if (kyberPreKey != null) result.kyberPreKey = kyberPreKey;
|
||||
if (kyberPreKeySignature != null)
|
||||
result.kyberPreKeySignature = kyberPreKeySignature;
|
||||
return result;
|
||||
}
|
||||
|
||||
ApplicationData_PqcPreKey._();
|
||||
|
||||
factory ApplicationData_PqcPreKey.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory ApplicationData_PqcPreKey.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'ApplicationData.PqcPreKey',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'eccPreKeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'eccPreKey', $pb.PbFieldType.OY)
|
||||
..aInt64(3, _omitFieldNames ? '' : 'kyberPreKeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'kyberPreKey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'kyberPreKeySignature', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_PqcPreKey clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_PqcPreKey copyWith(
|
||||
void Function(ApplicationData_PqcPreKey) updates) =>
|
||||
super.copyWith((message) => updates(message as ApplicationData_PqcPreKey))
|
||||
as ApplicationData_PqcPreKey;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_PqcPreKey create() => ApplicationData_PqcPreKey._();
|
||||
@$core.override
|
||||
ApplicationData_PqcPreKey createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_PqcPreKey getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<ApplicationData_PqcPreKey>(create);
|
||||
static ApplicationData_PqcPreKey? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get eccPreKeyId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set eccPreKeyId($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEccPreKeyId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEccPreKeyId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get eccPreKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set eccPreKey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEccPreKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEccPreKey() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get kyberPreKeyId => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set kyberPreKeyId($fixnum.Int64 value) => $_setInt64(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasKyberPreKeyId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearKyberPreKeyId() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get kyberPreKey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set kyberPreKey($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasKyberPreKey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearKyberPreKey() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get kyberPreKeySignature => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set kyberPreKeySignature($core.List<$core.int> value) => $_setBytes(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasKyberPreKeySignature() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearKyberPreKeySignature() => $_clearField(5);
|
||||
}
|
||||
|
||||
class ApplicationData_UploadPqcPreKeys extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_UploadPqcPreKeys({
|
||||
$fixnum.Int64? eccSignedPrekeyId,
|
||||
$core.List<$core.int>? eccSignedPrekey,
|
||||
$core.List<$core.int>? eccSignedPrekeySignature,
|
||||
$fixnum.Int64? kyberSignedPrekeyId,
|
||||
$core.List<$core.int>? kyberSignedPrekey,
|
||||
$core.List<$core.int>? kyberSignedPrekeySignature,
|
||||
$core.Iterable<ApplicationData_PqcPreKey>? prekeys,
|
||||
}) {
|
||||
final result = create();
|
||||
if (eccSignedPrekeyId != null) result.eccSignedPrekeyId = eccSignedPrekeyId;
|
||||
if (eccSignedPrekey != null) result.eccSignedPrekey = eccSignedPrekey;
|
||||
if (eccSignedPrekeySignature != null)
|
||||
result.eccSignedPrekeySignature = eccSignedPrekeySignature;
|
||||
if (kyberSignedPrekeyId != null)
|
||||
result.kyberSignedPrekeyId = kyberSignedPrekeyId;
|
||||
if (kyberSignedPrekey != null) result.kyberSignedPrekey = kyberSignedPrekey;
|
||||
if (kyberSignedPrekeySignature != null)
|
||||
result.kyberSignedPrekeySignature = kyberSignedPrekeySignature;
|
||||
if (prekeys != null) result.prekeys.addAll(prekeys);
|
||||
return result;
|
||||
}
|
||||
|
||||
ApplicationData_UploadPqcPreKeys._();
|
||||
|
||||
factory ApplicationData_UploadPqcPreKeys.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory ApplicationData_UploadPqcPreKeys.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'ApplicationData.UploadPqcPreKeys',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'eccSignedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'eccSignedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(3,
|
||||
_omitFieldNames ? '' : 'eccSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..aInt64(4, _omitFieldNames ? '' : 'kyberSignedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'kyberSignedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(6,
|
||||
_omitFieldNames ? '' : 'kyberSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..pPM<ApplicationData_PqcPreKey>(7, _omitFieldNames ? '' : 'prekeys',
|
||||
subBuilder: ApplicationData_PqcPreKey.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_UploadPqcPreKeys clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_UploadPqcPreKeys copyWith(
|
||||
void Function(ApplicationData_UploadPqcPreKeys) updates) =>
|
||||
super.copyWith(
|
||||
(message) => updates(message as ApplicationData_UploadPqcPreKeys))
|
||||
as ApplicationData_UploadPqcPreKeys;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_UploadPqcPreKeys create() =>
|
||||
ApplicationData_UploadPqcPreKeys._();
|
||||
@$core.override
|
||||
ApplicationData_UploadPqcPreKeys createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_UploadPqcPreKeys getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<ApplicationData_UploadPqcPreKeys>(
|
||||
create);
|
||||
static ApplicationData_UploadPqcPreKeys? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get eccSignedPrekeyId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set eccSignedPrekeyId($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEccSignedPrekeyId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEccSignedPrekeyId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get eccSignedPrekey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set eccSignedPrekey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEccSignedPrekey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEccSignedPrekey() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get eccSignedPrekeySignature => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set eccSignedPrekeySignature($core.List<$core.int> value) =>
|
||||
$_setBytes(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasEccSignedPrekeySignature() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearEccSignedPrekeySignature() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$fixnum.Int64 get kyberSignedPrekeyId => $_getI64(3);
|
||||
@$pb.TagNumber(4)
|
||||
set kyberSignedPrekeyId($fixnum.Int64 value) => $_setInt64(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasKyberSignedPrekeyId() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearKyberSignedPrekeyId() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get kyberSignedPrekey => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set kyberSignedPrekey($core.List<$core.int> value) => $_setBytes(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasKyberSignedPrekey() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearKyberSignedPrekey() => $_clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.List<$core.int> get kyberSignedPrekeySignature => $_getN(5);
|
||||
@$pb.TagNumber(6)
|
||||
set kyberSignedPrekeySignature($core.List<$core.int> value) =>
|
||||
$_setBytes(5, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasKyberSignedPrekeySignature() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearKyberSignedPrekeySignature() => $_clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$pb.PbList<ApplicationData_PqcPreKey> get prekeys => $_getList(6);
|
||||
}
|
||||
|
||||
class ApplicationData_DownloadDone extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_DownloadDone({
|
||||
$core.List<$core.int>? downloadToken,
|
||||
|
|
@ -3085,6 +3329,7 @@ enum ApplicationData_ApplicationData {
|
|||
getMemoriesUsage,
|
||||
deleteMemory,
|
||||
disableMemoriesBackup,
|
||||
uploadPqcPrekeys,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -3117,6 +3362,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
ApplicationData_GetMemoriesUsage? getMemoriesUsage,
|
||||
ApplicationData_DeleteMemory? deleteMemory,
|
||||
ApplicationData_DisableMemoriesBackup? disableMemoriesBackup,
|
||||
ApplicationData_UploadPqcPreKeys? uploadPqcPrekeys,
|
||||
}) {
|
||||
final result = create();
|
||||
if (textMessage != null) result.textMessage = textMessage;
|
||||
|
|
@ -3157,6 +3403,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
if (deleteMemory != null) result.deleteMemory = deleteMemory;
|
||||
if (disableMemoriesBackup != null)
|
||||
result.disableMemoriesBackup = disableMemoriesBackup;
|
||||
if (uploadPqcPrekeys != null) result.uploadPqcPrekeys = uploadPqcPrekeys;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -3198,6 +3445,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
37: ApplicationData_ApplicationData.getMemoriesUsage,
|
||||
38: ApplicationData_ApplicationData.deleteMemory,
|
||||
39: ApplicationData_ApplicationData.disableMemoriesBackup,
|
||||
40: ApplicationData_ApplicationData.uploadPqcPrekeys,
|
||||
0: ApplicationData_ApplicationData.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -3232,7 +3480,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
36,
|
||||
37,
|
||||
38,
|
||||
39
|
||||
39,
|
||||
40
|
||||
])
|
||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
||||
protoName: 'textMessage',
|
||||
|
|
@ -3327,6 +3576,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
..aOM<ApplicationData_DisableMemoriesBackup>(
|
||||
39, _omitFieldNames ? '' : 'disableMemoriesBackup',
|
||||
subBuilder: ApplicationData_DisableMemoriesBackup.create)
|
||||
..aOM<ApplicationData_UploadPqcPreKeys>(
|
||||
40, _omitFieldNames ? '' : 'uploadPqcPrekeys',
|
||||
subBuilder: ApplicationData_UploadPqcPreKeys.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -3375,6 +3627,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(37)
|
||||
@$pb.TagNumber(38)
|
||||
@$pb.TagNumber(39)
|
||||
@$pb.TagNumber(40)
|
||||
ApplicationData_ApplicationData whichApplicationData() =>
|
||||
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -3404,6 +3657,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(37)
|
||||
@$pb.TagNumber(38)
|
||||
@$pb.TagNumber(39)
|
||||
@$pb.TagNumber(40)
|
||||
void clearApplicationData() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -3737,6 +3991,18 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(39)
|
||||
ApplicationData_DisableMemoriesBackup ensureDisableMemoriesBackup() =>
|
||||
$_ensure(26);
|
||||
|
||||
@$pb.TagNumber(40)
|
||||
ApplicationData_UploadPqcPreKeys get uploadPqcPrekeys => $_getN(27);
|
||||
@$pb.TagNumber(40)
|
||||
set uploadPqcPrekeys(ApplicationData_UploadPqcPreKeys value) =>
|
||||
$_setField(40, value);
|
||||
@$pb.TagNumber(40)
|
||||
$core.bool hasUploadPqcPrekeys() => $_has(27);
|
||||
@$pb.TagNumber(40)
|
||||
void clearUploadPqcPrekeys() => $_clearField(40);
|
||||
@$pb.TagNumber(40)
|
||||
ApplicationData_UploadPqcPreKeys ensureUploadPqcPrekeys() => $_ensure(27);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
|
|
@ -3857,16 +4123,68 @@ class Response_Prekeys extends $pb.GeneratedMessage {
|
|||
$pb.PbList<Response_PreKey> get prekeys => $_getList(0);
|
||||
}
|
||||
|
||||
enum Response_Ok_Ok { none, prekeys, notSet }
|
||||
class Response_PqcPrekeys extends $pb.GeneratedMessage {
|
||||
factory Response_PqcPrekeys({
|
||||
$core.Iterable<ApplicationData_PqcPreKey>? prekeys,
|
||||
}) {
|
||||
final result = create();
|
||||
if (prekeys != null) result.prekeys.addAll(prekeys);
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_PqcPrekeys._();
|
||||
|
||||
factory Response_PqcPrekeys.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_PqcPrekeys.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.PqcPrekeys',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..pPM<ApplicationData_PqcPreKey>(1, _omitFieldNames ? '' : 'prekeys',
|
||||
subBuilder: ApplicationData_PqcPreKey.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PqcPrekeys clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PqcPrekeys copyWith(void Function(Response_PqcPrekeys) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_PqcPrekeys))
|
||||
as Response_PqcPrekeys;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PqcPrekeys create() => Response_PqcPrekeys._();
|
||||
@$core.override
|
||||
Response_PqcPrekeys createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PqcPrekeys getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_PqcPrekeys>(create);
|
||||
static Response_PqcPrekeys? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$pb.PbList<ApplicationData_PqcPreKey> get prekeys => $_getList(0);
|
||||
}
|
||||
|
||||
enum Response_Ok_Ok { none, prekeys, prekeysPqc, notSet }
|
||||
|
||||
class Response_Ok extends $pb.GeneratedMessage {
|
||||
factory Response_Ok({
|
||||
$core.bool? none,
|
||||
Response_Prekeys? prekeys,
|
||||
Response_PqcPrekeys? prekeysPqc,
|
||||
}) {
|
||||
final result = create();
|
||||
if (none != null) result.none = none;
|
||||
if (prekeys != null) result.prekeys = prekeys;
|
||||
if (prekeysPqc != null) result.prekeysPqc = prekeysPqc;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -3882,6 +4200,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
static const $core.Map<$core.int, Response_Ok_Ok> _Response_Ok_OkByTag = {
|
||||
1: Response_Ok_Ok.none,
|
||||
2: Response_Ok_Ok.prekeys,
|
||||
3: Response_Ok_Ok.prekeysPqc,
|
||||
0: Response_Ok_Ok.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -3889,10 +4208,12 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [1, 2])
|
||||
..oo(0, [1, 2, 3])
|
||||
..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None')
|
||||
..aOM<Response_Prekeys>(2, _omitFieldNames ? '' : 'prekeys',
|
||||
subBuilder: Response_Prekeys.create)
|
||||
..aOM<Response_PqcPrekeys>(3, _omitFieldNames ? '' : 'prekeysPqc',
|
||||
subBuilder: Response_PqcPrekeys.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -3916,9 +4237,11 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
|
||||
@$pb.TagNumber(1)
|
||||
@$pb.TagNumber(2)
|
||||
@$pb.TagNumber(3)
|
||||
Response_Ok_Ok whichOk() => _Response_Ok_OkByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
@$pb.TagNumber(2)
|
||||
@$pb.TagNumber(3)
|
||||
void clearOk() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -3940,6 +4263,17 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
void clearPrekeys() => $_clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
Response_Prekeys ensurePrekeys() => $_ensure(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
Response_PqcPrekeys get prekeysPqc => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set prekeysPqc(Response_PqcPrekeys value) => $_setField(3, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasPrekeysPqc() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearPrekeysPqc() => $_clearField(3);
|
||||
@$pb.TagNumber(3)
|
||||
Response_PqcPrekeys ensurePrekeysPqc() => $_ensure(2);
|
||||
}
|
||||
|
||||
enum Response_Response { ok, error, notSet }
|
||||
|
|
|
|||
|
|
@ -724,6 +724,15 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'disableMemoriesBackup'
|
||||
},
|
||||
{
|
||||
'1': 'upload_pqc_prekeys',
|
||||
'3': 40,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.UploadPqcPreKeys',
|
||||
'9': 0,
|
||||
'10': 'uploadPqcPrekeys'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
ApplicationData_TextMessage$json,
|
||||
|
|
@ -737,6 +746,8 @@ const ApplicationData$json = {
|
|||
ApplicationData_GetPrekeysByUserId$json,
|
||||
ApplicationData_GetSignedPreKeyByUserId$json,
|
||||
ApplicationData_UpdateSignedPreKey$json,
|
||||
ApplicationData_PqcPreKey$json,
|
||||
ApplicationData_UploadPqcPreKeys$json,
|
||||
ApplicationData_DownloadDone$json,
|
||||
ApplicationData_ReportUser$json,
|
||||
ApplicationData_IPAPurchase$json,
|
||||
|
|
@ -868,6 +879,81 @@ const ApplicationData_UpdateSignedPreKey$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_PqcPreKey$json = {
|
||||
'1': 'PqcPreKey',
|
||||
'2': [
|
||||
{'1': 'ecc_pre_key_id', '3': 1, '4': 1, '5': 3, '10': 'eccPreKeyId'},
|
||||
{'1': 'ecc_pre_key', '3': 2, '4': 1, '5': 12, '10': 'eccPreKey'},
|
||||
{'1': 'kyber_pre_key_id', '3': 3, '4': 1, '5': 3, '10': 'kyberPreKeyId'},
|
||||
{'1': 'kyber_pre_key', '3': 4, '4': 1, '5': 12, '10': 'kyberPreKey'},
|
||||
{
|
||||
'1': 'kyber_pre_key_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberPreKeySignature'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_UploadPqcPreKeys$json = {
|
||||
'1': 'UploadPqcPreKeys',
|
||||
'2': [
|
||||
{
|
||||
'1': 'ecc_signed_prekey_id',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'eccSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey_signature',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_id',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'kyberSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_signature',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'prekeys',
|
||||
'3': 7,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.PqcPreKey',
|
||||
'10': 'prekeys'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_DownloadDone$json = {
|
||||
'1': 'DownloadDone',
|
||||
|
|
@ -1073,38 +1159,51 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'CzIuLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkRlbGV0ZU1lbW9yeUgAUgxkZW'
|
||||
'xldGVNZW1vcnkScQoXZGlzYWJsZV9tZW1vcmllc19iYWNrdXAYJyABKAsyNy5jbGllbnRfdG9f'
|
||||
'c2VydmVyLkFwcGxpY2F0aW9uRGF0YS5EaXNhYmxlTWVtb3JpZXNCYWNrdXBIAFIVZGlzYWJsZU'
|
||||
'1lbW9yaWVzQmFja3VwGmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIS'
|
||||
'CgRib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNoRGF0YYgBAUIMCg'
|
||||
'pfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2Vy'
|
||||
'bmFtZRosCg5DaGFuZ2VVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaNQoUVX'
|
||||
'BkYXRlR29vZ2xlRmNtVG9rZW4SHQoKZ29vZ2xlX2ZjbRgBIAEoCVIJZ29vZ2xlRmNtGiYKC0dl'
|
||||
'dFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoTChFHZXRBdmFpbGFibGVQbGFucx'
|
||||
'oVChNHZXRDdXJyZW50UGxhbkluZm9zGi8KFFJlbW92ZUFkZGl0aW9uYWxVc2VyEhcKB3VzZXJf'
|
||||
'aWQYASABKANSBnVzZXJJZBotChJHZXRQcmVrZXlzQnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1'
|
||||
'IGdXNlcklkGjIKF0dldFNpZ25lZFByZUtleUJ5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVz'
|
||||
'ZXJJZBqbAQoSVXBkYXRlU2lnbmVkUHJlS2V5EigKEHNpZ25lZF9wcmVrZXlfaWQYASABKANSDn'
|
||||
'NpZ25lZFByZWtleUlkEiMKDXNpZ25lZF9wcmVrZXkYAiABKAxSDHNpZ25lZFByZWtleRI2Chdz'
|
||||
'aWduZWRfcHJla2V5X3NpZ25hdHVyZRgDIAEoDFIVc2lnbmVkUHJla2V5U2lnbmF0dXJlGjUKDE'
|
||||
'Rvd25sb2FkRG9uZRIlCg5kb3dubG9hZF90b2tlbhgBIAEoDFINZG93bmxvYWRUb2tlbhpOCgpS'
|
||||
'ZXBvcnRVc2VyEigKEHJlcG9ydGVkX3VzZXJfaWQYASABKANSDnJlcG9ydGVkVXNlcklkEhYKBn'
|
||||
'JlYXNvbhgCIAEoCVIGcmVhc29uGnEKC0lQQVB1cmNoYXNlEh0KCnByb2R1Y3RfaWQYASABKAlS'
|
||||
'CXByb2R1Y3RJZBIWCgZzb3VyY2UYAiABKAlSBnNvdXJjZRIrChF2ZXJpZmljYXRpb25fZGF0YR'
|
||||
'gDIAEoCVIQdmVyaWZpY2F0aW9uRGF0YRoPCg1JUEFGb3JjZUNoZWNrGg8KDURlbGV0ZUFjY291'
|
||||
'bnQaLAoRQWRkQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGjAKDVNldE'
|
||||
'xvZ2luVG9rZW4SHwoLbG9naW5fdG9rZW4YASABKAxSCmxvZ2luVG9rZW4ajgEKHFJlZ2lzdGVy'
|
||||
'UGFzc3dvcmRMZXNzUmVjb3ZlcnkSLgoSZW5jcnlwdGVkU2VydmVyS2V5GAEgASgMUhJlbmNyeX'
|
||||
'B0ZWRTZXJ2ZXJLZXkSKwoOcGluVW5sb2NrVG9rZW4YAiABKAxIAFIOcGluVW5sb2NrVG9rZW6I'
|
||||
'AQFCEQoPX3BpblVubG9ja1Rva2VuGnAKGFBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3'
|
||||
'RpZmljYXRpb25faWQYASABKAlSDm5vdGlmaWNhdGlvbklkEisKEWVuY3J5cHRlZF9tZXNzYWdl'
|
||||
'GAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdlGmsKFVJlcXVlc3RNZW1vcmllc1VwbG9hZBISCgRzaX'
|
||||
'plGAEgASgDUgRzaXplEiMKDW9yaWdpbmFsX2RhdGUYAiABKANSDG9yaWdpbmFsRGF0ZRIZCght'
|
||||
'ZWRpYV9pZBgDIAEoCVIHbWVkaWFJZBoyChVDb25maXJtTWVtb3JpZXNVcGxvYWQSGQoIbWVkaW'
|
||||
'FfaWQYASABKAlSB21lZGlhSWQaSAoPR2V0TWVtb3JpZXNMaXN0Eh8KC29mZnNldF9kYXRlGAEg'
|
||||
'ASgDUgpvZmZzZXREYXRlEhQKBWxpbWl0GAIgASgDUgVsaW1pdBpJCg5HZXRNZW1vcmllc1VybB'
|
||||
'IZCghtZWRpYV9pZBgBIAEoCVIHbWVkaWFJZBIcCgl0aHVtYm5haWwYAiABKAhSCXRodW1ibmFp'
|
||||
'bBoSChBHZXRNZW1vcmllc1VzYWdlGikKDERlbGV0ZU1lbW9yeRIZCghtZWRpYV9pZBgBIAEoCV'
|
||||
'IHbWVkaWFJZBoXChVEaXNhYmxlTWVtb3JpZXNCYWNrdXBCEQoPQXBwbGljYXRpb25EYXRhSgQI'
|
||||
'CRAKSgQICxAMSgQIDRASSgQIExAU');
|
||||
'1lbW9yaWVzQmFja3VwEmIKEnVwbG9hZF9wcWNfcHJla2V5cxgoIAEoCzIyLmNsaWVudF90b19z'
|
||||
'ZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwbG9hZFBxY1ByZUtleXNIAFIQdXBsb2FkUHFjUHJla2'
|
||||
'V5cxpqCgtUZXh0TWVzc2FnZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEo'
|
||||
'DFIEYm9keRIgCglwdXNoX2RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YR'
|
||||
'ovChFHZXRVc2VyQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hh'
|
||||
'bmdlVXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZU'
|
||||
'ZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIX'
|
||||
'Cgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFQoTR2V0Q3Vycm'
|
||||
'VudFBsYW5JbmZvcxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1'
|
||||
'c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyCh'
|
||||
'dHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVw'
|
||||
'ZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZX'
|
||||
'lJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5Ei'
|
||||
'MKDmVjY19wcmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEo'
|
||||
'DFIJZWNjUHJlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIg'
|
||||
'oNa3liZXJfcHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWdu'
|
||||
'YXR1cmUYBSABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGp0DChBVcGxvYWRQcWNQcmVLZXlzEi'
|
||||
'8KFGVjY19zaWduZWRfcHJla2V5X2lkGAEgASgDUhFlY2NTaWduZWRQcmVrZXlJZBIqChFlY2Nf'
|
||||
'c2lnbmVkX3ByZWtleRgCIAEoDFIPZWNjU2lnbmVkUHJla2V5Ej0KG2VjY19zaWduZWRfcHJla2'
|
||||
'V5X3NpZ25hdHVyZRgDIAEoDFIYZWNjU2lnbmVkUHJla2V5U2lnbmF0dXJlEjMKFmt5YmVyX3Np'
|
||||
'Z25lZF9wcmVrZXlfaWQYBCABKANSE2t5YmVyU2lnbmVkUHJla2V5SWQSLgoTa3liZXJfc2lnbm'
|
||||
'VkX3ByZWtleRgFIAEoDFIRa3liZXJTaWduZWRQcmVrZXkSQQoda3liZXJfc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmUYBiABKAxSGmt5YmVyU2lnbmVkUHJla2V5U2lnbmF0dXJlEkUKB3ByZWtleX'
|
||||
'MYByADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3By'
|
||||
'ZWtleXMaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZF'
|
||||
'Rva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRV'
|
||||
'c2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF'
|
||||
'9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNh'
|
||||
'dGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRG'
|
||||
'VsZXRlQWNjb3VudBosChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2Vy'
|
||||
'SWQaMAoNU2V0TG9naW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhqOAQ'
|
||||
'ocUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeRIuChJlbmNyeXB0ZWRTZXJ2ZXJLZXkYASAB'
|
||||
'KAxSEmVuY3J5cHRlZFNlcnZlcktleRIrCg5waW5VbmxvY2tUb2tlbhgCIAEoDEgAUg5waW5Vbm'
|
||||
'xvY2tUb2tlbogBAUIRCg9fcGluVW5sb2NrVG9rZW4acAoYUGFzc3dvcmRsZXNzTm90aWZpY2F0'
|
||||
'aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSKwoRZW5jcnlwdG'
|
||||
'VkX21lc3NhZ2UYAiABKAxSEGVuY3J5cHRlZE1lc3NhZ2UaawoVUmVxdWVzdE1lbW9yaWVzVXBs'
|
||||
'b2FkEhIKBHNpemUYASABKANSBHNpemUSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3JpZ2luYW'
|
||||
'xEYXRlEhkKCG1lZGlhX2lkGAMgASgJUgdtZWRpYUlkGjIKFUNvbmZpcm1NZW1vcmllc1VwbG9h'
|
||||
'ZBIZCghtZWRpYV9pZBgBIAEoCVIHbWVkaWFJZBpICg9HZXRNZW1vcmllc0xpc3QSHwoLb2Zmc2'
|
||||
'V0X2RhdGUYASABKANSCm9mZnNldERhdGUSFAoFbGltaXQYAiABKANSBWxpbWl0GkkKDkdldE1l'
|
||||
'bW9yaWVzVXJsEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkEhwKCXRodW1ibmFpbBgCIAEoCF'
|
||||
'IJdGh1bWJuYWlsGhIKEEdldE1lbW9yaWVzVXNhZ2UaKQoMRGVsZXRlTWVtb3J5EhkKCG1lZGlh'
|
||||
'X2lkGAEgASgJUgdtZWRpYUlkGhcKFURpc2FibGVNZW1vcmllc0JhY2t1cEIRCg9BcHBsaWNhdG'
|
||||
'lvbkRhdGFKBAgJEApKBAgLEAxKBAgNEBJKBAgTEBQ=');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
@ -1129,7 +1228,12 @@ const Response$json = {
|
|||
'10': 'error'
|
||||
},
|
||||
],
|
||||
'3': [Response_PreKey$json, Response_Prekeys$json, Response_Ok$json],
|
||||
'3': [
|
||||
Response_PreKey$json,
|
||||
Response_Prekeys$json,
|
||||
Response_PqcPrekeys$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Response'},
|
||||
],
|
||||
|
|
@ -1159,6 +1263,21 @@ const Response_Prekeys$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PqcPrekeys$json = {
|
||||
'1': 'PqcPrekeys',
|
||||
'2': [
|
||||
{
|
||||
'1': 'prekeys',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.PqcPreKey',
|
||||
'10': 'prekeys'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Ok$json = {
|
||||
'1': 'Ok',
|
||||
|
|
@ -1173,6 +1292,15 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'prekeys'
|
||||
},
|
||||
{
|
||||
'1': 'prekeys_pqc',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Response.PqcPrekeys',
|
||||
'9': 0,
|
||||
'10': 'prekeysPqc'
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Ok'},
|
||||
|
|
@ -1185,5 +1313,8 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'ICb2sSKAoFZXJyb3IYAiABKA4yEC5lcnJvci5FcnJvckNvZGVIAFIFZXJyb3IaMAoGUHJlS2V5'
|
||||
'Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkYAiABKAxSBnByZWtleRpGCgdQcmVrZXlzEjsKB3'
|
||||
'ByZWtleXMYASADKAsyIS5jbGllbnRfdG9fc2VydmVyLlJlc3BvbnNlLlByZUtleVIHcHJla2V5'
|
||||
'cxpgCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSPgoHcHJla2V5cxgCIAEoCzIiLmNsaWVudF'
|
||||
'90b19zZXJ2ZXIuUmVzcG9uc2UuUHJla2V5c0gAUgdwcmVrZXlzQgQKAk9rQgoKCFJlc3BvbnNl');
|
||||
'cxpTCgpQcWNQcmVrZXlzEkUKB3ByZWtleXMYASADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcG'
|
||||
'xpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3ByZWtleXMaqgEKAk9rEhQKBE5vbmUYASABKAhIAFIE'
|
||||
'Tm9uZRI+CgdwcmVrZXlzGAIgASgLMiIuY2xpZW50X3RvX3NlcnZlci5SZXNwb25zZS5QcmVrZX'
|
||||
'lzSABSB3ByZWtleXMSSAoLcHJla2V5c19wcWMYAyABKAsyJS5jbGllbnRfdG9fc2VydmVyLlJl'
|
||||
'c3BvbnNlLlBxY1ByZWtleXNIAFIKcHJla2V5c1BxY0IECgJPa0IKCghSZXNwb25zZQ==');
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ enum V0_Kind {
|
|||
requestNewPreKeys,
|
||||
error,
|
||||
newMessages,
|
||||
requestNewPqcPreKeys,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
$core.bool? requestNewPreKeys,
|
||||
$0.ErrorCode? error,
|
||||
NewMessages? newMessages,
|
||||
$core.bool? requestNewPqcPreKeys,
|
||||
}) {
|
||||
final result = create();
|
||||
if (seq != null) result.seq = seq;
|
||||
|
|
@ -114,6 +116,8 @@ class V0 extends $pb.GeneratedMessage {
|
|||
if (requestNewPreKeys != null) result.requestNewPreKeys = requestNewPreKeys;
|
||||
if (error != null) result.error = error;
|
||||
if (newMessages != null) result.newMessages = newMessages;
|
||||
if (requestNewPqcPreKeys != null)
|
||||
result.requestNewPqcPreKeys = requestNewPqcPreKeys;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +136,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
4: V0_Kind.requestNewPreKeys,
|
||||
6: V0_Kind.error,
|
||||
7: V0_Kind.newMessages,
|
||||
8: V0_Kind.requestNewPqcPreKeys,
|
||||
0: V0_Kind.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -139,7 +144,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [2, 3, 4, 6, 7])
|
||||
..oo(0, [2, 3, 4, 6, 7, 8])
|
||||
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6,
|
||||
defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..aOM<Response>(2, _omitFieldNames ? '' : 'response',
|
||||
|
|
@ -152,6 +157,8 @@ class V0 extends $pb.GeneratedMessage {
|
|||
enumValues: $0.ErrorCode.values)
|
||||
..aOM<NewMessages>(7, _omitFieldNames ? '' : 'newMessages',
|
||||
protoName: 'newMessages', subBuilder: NewMessages.create)
|
||||
..aOB(8, _omitFieldNames ? '' : 'RequestNewPqcPreKeys',
|
||||
protoName: 'RequestNewPqcPreKeys')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -177,12 +184,14 @@ class V0 extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
V0_Kind whichKind() => _V0_KindByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(2)
|
||||
@$pb.TagNumber(3)
|
||||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
void clearKind() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -244,6 +253,15 @@ class V0 extends $pb.GeneratedMessage {
|
|||
void clearNewMessages() => $_clearField(7);
|
||||
@$pb.TagNumber(7)
|
||||
NewMessages ensureNewMessages() => $_ensure(5);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool get requestNewPqcPreKeys => $_getBF(6);
|
||||
@$pb.TagNumber(8)
|
||||
set requestNewPqcPreKeys($core.bool value) => $_setBool(6, value);
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasRequestNewPqcPreKeys() => $_has(6);
|
||||
@$pb.TagNumber(8)
|
||||
void clearRequestNewPqcPreKeys() => $_clearField(8);
|
||||
}
|
||||
|
||||
class NewMessage extends $pb.GeneratedMessage {
|
||||
|
|
@ -830,45 +848,6 @@ class Response_AdditionalAccount extends $pb.GeneratedMessage {
|
|||
void clearPlanId() => $_clearField(3);
|
||||
}
|
||||
|
||||
class Response_Deprecated extends $pb.GeneratedMessage {
|
||||
factory Response_Deprecated() => create();
|
||||
|
||||
Response_Deprecated._();
|
||||
|
||||
factory Response_Deprecated.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_Deprecated.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.Deprecated',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Deprecated clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Deprecated copyWith(void Function(Response_Deprecated) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Deprecated))
|
||||
as Response_Deprecated;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Deprecated create() => Response_Deprecated._();
|
||||
@$core.override
|
||||
Response_Deprecated createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Deprecated getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Deprecated>(create);
|
||||
static Response_Deprecated? _defaultInstance;
|
||||
}
|
||||
|
||||
class Response_Transaction extends $pb.GeneratedMessage {
|
||||
factory Response_Transaction() => create();
|
||||
|
||||
|
|
@ -1196,6 +1175,252 @@ class Response_SignedPreKey extends $pb.GeneratedMessage {
|
|||
void clearSignedPrekeySignature() => $_clearField(3);
|
||||
}
|
||||
|
||||
class Response_PqcPreKey extends $pb.GeneratedMessage {
|
||||
factory Response_PqcPreKey({
|
||||
$fixnum.Int64? eccPreKeyId,
|
||||
$core.List<$core.int>? eccPreKey,
|
||||
$fixnum.Int64? kyberPreKeyId,
|
||||
$core.List<$core.int>? kyberPreKey,
|
||||
$core.List<$core.int>? kyberPreKeySignature,
|
||||
}) {
|
||||
final result = create();
|
||||
if (eccPreKeyId != null) result.eccPreKeyId = eccPreKeyId;
|
||||
if (eccPreKey != null) result.eccPreKey = eccPreKey;
|
||||
if (kyberPreKeyId != null) result.kyberPreKeyId = kyberPreKeyId;
|
||||
if (kyberPreKey != null) result.kyberPreKey = kyberPreKey;
|
||||
if (kyberPreKeySignature != null)
|
||||
result.kyberPreKeySignature = kyberPreKeySignature;
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_PqcPreKey._();
|
||||
|
||||
factory Response_PqcPreKey.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_PqcPreKey.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.PqcPreKey',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'eccPreKeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'eccPreKey', $pb.PbFieldType.OY)
|
||||
..aInt64(3, _omitFieldNames ? '' : 'kyberPreKeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'kyberPreKey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'kyberPreKeySignature', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PqcPreKey clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PqcPreKey copyWith(void Function(Response_PqcPreKey) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_PqcPreKey))
|
||||
as Response_PqcPreKey;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PqcPreKey create() => Response_PqcPreKey._();
|
||||
@$core.override
|
||||
Response_PqcPreKey createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PqcPreKey getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_PqcPreKey>(create);
|
||||
static Response_PqcPreKey? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get eccPreKeyId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set eccPreKeyId($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEccPreKeyId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEccPreKeyId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get eccPreKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set eccPreKey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEccPreKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEccPreKey() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get kyberPreKeyId => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set kyberPreKeyId($fixnum.Int64 value) => $_setInt64(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasKyberPreKeyId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearKyberPreKeyId() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get kyberPreKey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set kyberPreKey($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasKyberPreKey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearKyberPreKey() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get kyberPreKeySignature => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set kyberPreKeySignature($core.List<$core.int> value) => $_setBytes(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasKyberPreKeySignature() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearKyberPreKeySignature() => $_clearField(5);
|
||||
}
|
||||
|
||||
class Response_PqcBundle extends $pb.GeneratedMessage {
|
||||
factory Response_PqcBundle({
|
||||
$fixnum.Int64? eccSignedPrekeyId,
|
||||
$core.List<$core.int>? eccSignedPrekey,
|
||||
$core.List<$core.int>? eccSignedPrekeySignature,
|
||||
$fixnum.Int64? kyberSignedPrekeyId,
|
||||
$core.List<$core.int>? kyberSignedPrekey,
|
||||
$core.List<$core.int>? kyberSignedPrekeySignature,
|
||||
Response_PqcPreKey? prekey,
|
||||
}) {
|
||||
final result = create();
|
||||
if (eccSignedPrekeyId != null) result.eccSignedPrekeyId = eccSignedPrekeyId;
|
||||
if (eccSignedPrekey != null) result.eccSignedPrekey = eccSignedPrekey;
|
||||
if (eccSignedPrekeySignature != null)
|
||||
result.eccSignedPrekeySignature = eccSignedPrekeySignature;
|
||||
if (kyberSignedPrekeyId != null)
|
||||
result.kyberSignedPrekeyId = kyberSignedPrekeyId;
|
||||
if (kyberSignedPrekey != null) result.kyberSignedPrekey = kyberSignedPrekey;
|
||||
if (kyberSignedPrekeySignature != null)
|
||||
result.kyberSignedPrekeySignature = kyberSignedPrekeySignature;
|
||||
if (prekey != null) result.prekey = prekey;
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_PqcBundle._();
|
||||
|
||||
factory Response_PqcBundle.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_PqcBundle.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.PqcBundle',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'eccSignedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'eccSignedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(3,
|
||||
_omitFieldNames ? '' : 'eccSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..aInt64(4, _omitFieldNames ? '' : 'kyberSignedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'kyberSignedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(6,
|
||||
_omitFieldNames ? '' : 'kyberSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..aOM<Response_PqcPreKey>(7, _omitFieldNames ? '' : 'prekey',
|
||||
subBuilder: Response_PqcPreKey.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PqcBundle clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PqcBundle copyWith(void Function(Response_PqcBundle) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_PqcBundle))
|
||||
as Response_PqcBundle;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PqcBundle create() => Response_PqcBundle._();
|
||||
@$core.override
|
||||
Response_PqcBundle createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PqcBundle getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_PqcBundle>(create);
|
||||
static Response_PqcBundle? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get eccSignedPrekeyId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set eccSignedPrekeyId($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEccSignedPrekeyId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEccSignedPrekeyId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get eccSignedPrekey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set eccSignedPrekey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEccSignedPrekey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEccSignedPrekey() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get eccSignedPrekeySignature => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set eccSignedPrekeySignature($core.List<$core.int> value) =>
|
||||
$_setBytes(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasEccSignedPrekeySignature() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearEccSignedPrekeySignature() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$fixnum.Int64 get kyberSignedPrekeyId => $_getI64(3);
|
||||
@$pb.TagNumber(4)
|
||||
set kyberSignedPrekeyId($fixnum.Int64 value) => $_setInt64(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasKyberSignedPrekeyId() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearKyberSignedPrekeyId() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get kyberSignedPrekey => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set kyberSignedPrekey($core.List<$core.int> value) => $_setBytes(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasKyberSignedPrekey() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearKyberSignedPrekey() => $_clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.List<$core.int> get kyberSignedPrekeySignature => $_getN(5);
|
||||
@$pb.TagNumber(6)
|
||||
set kyberSignedPrekeySignature($core.List<$core.int> value) =>
|
||||
$_setBytes(5, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasKyberSignedPrekeySignature() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearKyberSignedPrekeySignature() => $_clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
Response_PqcPreKey get prekey => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set prekey(Response_PqcPreKey value) => $_setField(7, value);
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasPrekey() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearPrekey() => $_clearField(7);
|
||||
@$pb.TagNumber(7)
|
||||
Response_PqcPreKey ensurePrekey() => $_ensure(6);
|
||||
}
|
||||
|
||||
class Response_UserData extends $pb.GeneratedMessage {
|
||||
factory Response_UserData({
|
||||
$fixnum.Int64? userId,
|
||||
|
|
@ -1206,6 +1431,7 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
$fixnum.Int64? signedPrekeyId,
|
||||
$core.List<$core.int>? username,
|
||||
$fixnum.Int64? registrationId,
|
||||
Response_PqcBundle? pqcBundle,
|
||||
}) {
|
||||
final result = create();
|
||||
if (userId != null) result.userId = userId;
|
||||
|
|
@ -1217,6 +1443,7 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
if (signedPrekeyId != null) result.signedPrekeyId = signedPrekeyId;
|
||||
if (username != null) result.username = username;
|
||||
if (registrationId != null) result.registrationId = registrationId;
|
||||
if (pqcBundle != null) result.pqcBundle = pqcBundle;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1247,6 +1474,8 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
..a<$core.List<$core.int>>(
|
||||
7, _omitFieldNames ? '' : 'username', $pb.PbFieldType.OY)
|
||||
..aInt64(8, _omitFieldNames ? '' : 'registrationId')
|
||||
..aOM<Response_PqcBundle>(9, _omitFieldNames ? '' : 'pqcBundle',
|
||||
subBuilder: Response_PqcBundle.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -1334,6 +1563,17 @@ class Response_UserData extends $pb.GeneratedMessage {
|
|||
$core.bool hasRegistrationId() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearRegistrationId() => $_clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
Response_PqcBundle get pqcBundle => $_getN(8);
|
||||
@$pb.TagNumber(9)
|
||||
set pqcBundle(Response_PqcBundle value) => $_setField(9, value);
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasPqcBundle() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearPqcBundle() => $_clearField(9);
|
||||
@$pb.TagNumber(9)
|
||||
Response_PqcBundle ensurePqcBundle() => $_ensure(8);
|
||||
}
|
||||
|
||||
class Response_UploadToken extends $pb.GeneratedMessage {
|
||||
|
|
@ -2074,11 +2314,9 @@ enum Response_Ok_Ok {
|
|||
uploadtoken,
|
||||
userdata,
|
||||
authtoken,
|
||||
deprecated7,
|
||||
authenticated,
|
||||
plans,
|
||||
planballance,
|
||||
deprecated11,
|
||||
addaccountsinvites,
|
||||
downloadtokens,
|
||||
signedprekey,
|
||||
|
|
@ -2100,11 +2338,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
Response_UploadToken? uploadtoken,
|
||||
Response_UserData? userdata,
|
||||
$core.List<$core.int>? authtoken,
|
||||
Response_Deprecated? deprecated7,
|
||||
Response_Authenticated? authenticated,
|
||||
Response_Plans? plans,
|
||||
Response_PlanBallance? planballance,
|
||||
Response_Deprecated? deprecated11,
|
||||
Response_AddAccountsInvites? addaccountsinvites,
|
||||
Response_DownloadTokens? downloadtokens,
|
||||
Response_SignedPreKey? signedprekey,
|
||||
|
|
@ -2123,11 +2359,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
if (uploadtoken != null) result.uploadtoken = uploadtoken;
|
||||
if (userdata != null) result.userdata = userdata;
|
||||
if (authtoken != null) result.authtoken = authtoken;
|
||||
if (deprecated7 != null) result.deprecated7 = deprecated7;
|
||||
if (authenticated != null) result.authenticated = authenticated;
|
||||
if (plans != null) result.plans = plans;
|
||||
if (planballance != null) result.planballance = planballance;
|
||||
if (deprecated11 != null) result.deprecated11 = deprecated11;
|
||||
if (addaccountsinvites != null)
|
||||
result.addaccountsinvites = addaccountsinvites;
|
||||
if (downloadtokens != null) result.downloadtokens = downloadtokens;
|
||||
|
|
@ -2162,11 +2396,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
4: Response_Ok_Ok.uploadtoken,
|
||||
5: Response_Ok_Ok.userdata,
|
||||
6: Response_Ok_Ok.authtoken,
|
||||
7: Response_Ok_Ok.deprecated7,
|
||||
8: Response_Ok_Ok.authenticated,
|
||||
9: Response_Ok_Ok.plans,
|
||||
10: Response_Ok_Ok.planballance,
|
||||
11: Response_Ok_Ok.deprecated11,
|
||||
12: Response_Ok_Ok.addaccountsinvites,
|
||||
13: Response_Ok_Ok.downloadtokens,
|
||||
14: Response_Ok_Ok.signedprekey,
|
||||
|
|
@ -2184,29 +2416,8 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21
|
||||
])
|
||||
..oo(
|
||||
0, [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21])
|
||||
..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None')
|
||||
..aInt64(2, _omitFieldNames ? '' : 'userid')
|
||||
..a<$core.List<$core.int>>(
|
||||
|
|
@ -2217,16 +2428,12 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
subBuilder: Response_UserData.create)
|
||||
..a<$core.List<$core.int>>(
|
||||
6, _omitFieldNames ? '' : 'authtoken', $pb.PbFieldType.OY)
|
||||
..aOM<Response_Deprecated>(7, _omitFieldNames ? '' : 'deprecated7',
|
||||
protoName: 'deprecated_7', subBuilder: Response_Deprecated.create)
|
||||
..aOM<Response_Authenticated>(8, _omitFieldNames ? '' : 'authenticated',
|
||||
subBuilder: Response_Authenticated.create)
|
||||
..aOM<Response_Plans>(9, _omitFieldNames ? '' : 'plans',
|
||||
subBuilder: Response_Plans.create)
|
||||
..aOM<Response_PlanBallance>(10, _omitFieldNames ? '' : 'planballance',
|
||||
subBuilder: Response_PlanBallance.create)
|
||||
..aOM<Response_Deprecated>(11, _omitFieldNames ? '' : 'deprecated11',
|
||||
protoName: 'deprecated_11', subBuilder: Response_Deprecated.create)
|
||||
..aOM<Response_AddAccountsInvites>(
|
||||
12, _omitFieldNames ? '' : 'addaccountsinvites',
|
||||
subBuilder: Response_AddAccountsInvites.create)
|
||||
|
|
@ -2279,11 +2486,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(5)
|
||||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
@$pb.TagNumber(9)
|
||||
@$pb.TagNumber(10)
|
||||
@$pb.TagNumber(11)
|
||||
@$pb.TagNumber(12)
|
||||
@$pb.TagNumber(13)
|
||||
@$pb.TagNumber(14)
|
||||
|
|
@ -2301,11 +2506,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(4)
|
||||
@$pb.TagNumber(5)
|
||||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
@$pb.TagNumber(9)
|
||||
@$pb.TagNumber(10)
|
||||
@$pb.TagNumber(11)
|
||||
@$pb.TagNumber(12)
|
||||
@$pb.TagNumber(13)
|
||||
@$pb.TagNumber(14)
|
||||
|
|
@ -2376,175 +2579,153 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(6)
|
||||
void clearAuthtoken() => $_clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
Response_Deprecated get deprecated7 => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set deprecated7(Response_Deprecated value) => $_setField(7, value);
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasDeprecated7() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearDeprecated7() => $_clearField(7);
|
||||
@$pb.TagNumber(7)
|
||||
Response_Deprecated ensureDeprecated7() => $_ensure(6);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
Response_Authenticated get authenticated => $_getN(7);
|
||||
Response_Authenticated get authenticated => $_getN(6);
|
||||
@$pb.TagNumber(8)
|
||||
set authenticated(Response_Authenticated value) => $_setField(8, value);
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasAuthenticated() => $_has(7);
|
||||
$core.bool hasAuthenticated() => $_has(6);
|
||||
@$pb.TagNumber(8)
|
||||
void clearAuthenticated() => $_clearField(8);
|
||||
@$pb.TagNumber(8)
|
||||
Response_Authenticated ensureAuthenticated() => $_ensure(7);
|
||||
Response_Authenticated ensureAuthenticated() => $_ensure(6);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
Response_Plans get plans => $_getN(8);
|
||||
Response_Plans get plans => $_getN(7);
|
||||
@$pb.TagNumber(9)
|
||||
set plans(Response_Plans value) => $_setField(9, value);
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasPlans() => $_has(8);
|
||||
$core.bool hasPlans() => $_has(7);
|
||||
@$pb.TagNumber(9)
|
||||
void clearPlans() => $_clearField(9);
|
||||
@$pb.TagNumber(9)
|
||||
Response_Plans ensurePlans() => $_ensure(8);
|
||||
Response_Plans ensurePlans() => $_ensure(7);
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
Response_PlanBallance get planballance => $_getN(9);
|
||||
Response_PlanBallance get planballance => $_getN(8);
|
||||
@$pb.TagNumber(10)
|
||||
set planballance(Response_PlanBallance value) => $_setField(10, value);
|
||||
@$pb.TagNumber(10)
|
||||
$core.bool hasPlanballance() => $_has(9);
|
||||
$core.bool hasPlanballance() => $_has(8);
|
||||
@$pb.TagNumber(10)
|
||||
void clearPlanballance() => $_clearField(10);
|
||||
@$pb.TagNumber(10)
|
||||
Response_PlanBallance ensurePlanballance() => $_ensure(9);
|
||||
|
||||
@$pb.TagNumber(11)
|
||||
Response_Deprecated get deprecated11 => $_getN(10);
|
||||
@$pb.TagNumber(11)
|
||||
set deprecated11(Response_Deprecated value) => $_setField(11, value);
|
||||
@$pb.TagNumber(11)
|
||||
$core.bool hasDeprecated11() => $_has(10);
|
||||
@$pb.TagNumber(11)
|
||||
void clearDeprecated11() => $_clearField(11);
|
||||
@$pb.TagNumber(11)
|
||||
Response_Deprecated ensureDeprecated11() => $_ensure(10);
|
||||
Response_PlanBallance ensurePlanballance() => $_ensure(8);
|
||||
|
||||
@$pb.TagNumber(12)
|
||||
Response_AddAccountsInvites get addaccountsinvites => $_getN(11);
|
||||
Response_AddAccountsInvites get addaccountsinvites => $_getN(9);
|
||||
@$pb.TagNumber(12)
|
||||
set addaccountsinvites(Response_AddAccountsInvites value) =>
|
||||
$_setField(12, value);
|
||||
@$pb.TagNumber(12)
|
||||
$core.bool hasAddaccountsinvites() => $_has(11);
|
||||
$core.bool hasAddaccountsinvites() => $_has(9);
|
||||
@$pb.TagNumber(12)
|
||||
void clearAddaccountsinvites() => $_clearField(12);
|
||||
@$pb.TagNumber(12)
|
||||
Response_AddAccountsInvites ensureAddaccountsinvites() => $_ensure(11);
|
||||
Response_AddAccountsInvites ensureAddaccountsinvites() => $_ensure(9);
|
||||
|
||||
@$pb.TagNumber(13)
|
||||
Response_DownloadTokens get downloadtokens => $_getN(12);
|
||||
Response_DownloadTokens get downloadtokens => $_getN(10);
|
||||
@$pb.TagNumber(13)
|
||||
set downloadtokens(Response_DownloadTokens value) => $_setField(13, value);
|
||||
@$pb.TagNumber(13)
|
||||
$core.bool hasDownloadtokens() => $_has(12);
|
||||
$core.bool hasDownloadtokens() => $_has(10);
|
||||
@$pb.TagNumber(13)
|
||||
void clearDownloadtokens() => $_clearField(13);
|
||||
@$pb.TagNumber(13)
|
||||
Response_DownloadTokens ensureDownloadtokens() => $_ensure(12);
|
||||
Response_DownloadTokens ensureDownloadtokens() => $_ensure(10);
|
||||
|
||||
@$pb.TagNumber(14)
|
||||
Response_SignedPreKey get signedprekey => $_getN(13);
|
||||
Response_SignedPreKey get signedprekey => $_getN(11);
|
||||
@$pb.TagNumber(14)
|
||||
set signedprekey(Response_SignedPreKey value) => $_setField(14, value);
|
||||
@$pb.TagNumber(14)
|
||||
$core.bool hasSignedprekey() => $_has(13);
|
||||
$core.bool hasSignedprekey() => $_has(11);
|
||||
@$pb.TagNumber(14)
|
||||
void clearSignedprekey() => $_clearField(14);
|
||||
@$pb.TagNumber(14)
|
||||
Response_SignedPreKey ensureSignedprekey() => $_ensure(13);
|
||||
Response_SignedPreKey ensureSignedprekey() => $_ensure(11);
|
||||
|
||||
@$pb.TagNumber(15)
|
||||
Response_ProofOfWork get proofOfWork => $_getN(14);
|
||||
Response_ProofOfWork get proofOfWork => $_getN(12);
|
||||
@$pb.TagNumber(15)
|
||||
set proofOfWork(Response_ProofOfWork value) => $_setField(15, value);
|
||||
@$pb.TagNumber(15)
|
||||
$core.bool hasProofOfWork() => $_has(14);
|
||||
$core.bool hasProofOfWork() => $_has(12);
|
||||
@$pb.TagNumber(15)
|
||||
void clearProofOfWork() => $_clearField(15);
|
||||
@$pb.TagNumber(15)
|
||||
Response_ProofOfWork ensureProofOfWork() => $_ensure(14);
|
||||
Response_ProofOfWork ensureProofOfWork() => $_ensure(12);
|
||||
|
||||
@$pb.TagNumber(16)
|
||||
$core.List<$core.int> get passwordlessRecoveryServerKey => $_getN(15);
|
||||
$core.List<$core.int> get passwordlessRecoveryServerKey => $_getN(13);
|
||||
@$pb.TagNumber(16)
|
||||
set passwordlessRecoveryServerKey($core.List<$core.int> value) =>
|
||||
$_setBytes(15, value);
|
||||
$_setBytes(13, value);
|
||||
@$pb.TagNumber(16)
|
||||
$core.bool hasPasswordlessRecoveryServerKey() => $_has(15);
|
||||
$core.bool hasPasswordlessRecoveryServerKey() => $_has(13);
|
||||
@$pb.TagNumber(16)
|
||||
void clearPasswordlessRecoveryServerKey() => $_clearField(16);
|
||||
|
||||
@$pb.TagNumber(17)
|
||||
Response_PasswordlessNotificationMessages
|
||||
get passwordlessNotificationMessages => $_getN(16);
|
||||
get passwordlessNotificationMessages => $_getN(14);
|
||||
@$pb.TagNumber(17)
|
||||
set passwordlessNotificationMessages(
|
||||
Response_PasswordlessNotificationMessages value) =>
|
||||
$_setField(17, value);
|
||||
@$pb.TagNumber(17)
|
||||
$core.bool hasPasswordlessNotificationMessages() => $_has(16);
|
||||
$core.bool hasPasswordlessNotificationMessages() => $_has(14);
|
||||
@$pb.TagNumber(17)
|
||||
void clearPasswordlessNotificationMessages() => $_clearField(17);
|
||||
@$pb.TagNumber(17)
|
||||
Response_PasswordlessNotificationMessages
|
||||
ensurePasswordlessNotificationMessages() => $_ensure(16);
|
||||
ensurePasswordlessNotificationMessages() => $_ensure(14);
|
||||
|
||||
@$pb.TagNumber(18)
|
||||
Response_MemoriesUploadUrls get memoriesUploadUrls => $_getN(17);
|
||||
Response_MemoriesUploadUrls get memoriesUploadUrls => $_getN(15);
|
||||
@$pb.TagNumber(18)
|
||||
set memoriesUploadUrls(Response_MemoriesUploadUrls value) =>
|
||||
$_setField(18, value);
|
||||
@$pb.TagNumber(18)
|
||||
$core.bool hasMemoriesUploadUrls() => $_has(17);
|
||||
$core.bool hasMemoriesUploadUrls() => $_has(15);
|
||||
@$pb.TagNumber(18)
|
||||
void clearMemoriesUploadUrls() => $_clearField(18);
|
||||
@$pb.TagNumber(18)
|
||||
Response_MemoriesUploadUrls ensureMemoriesUploadUrls() => $_ensure(17);
|
||||
Response_MemoriesUploadUrls ensureMemoriesUploadUrls() => $_ensure(15);
|
||||
|
||||
@$pb.TagNumber(19)
|
||||
Response_MemoriesList get memoriesList => $_getN(18);
|
||||
Response_MemoriesList get memoriesList => $_getN(16);
|
||||
@$pb.TagNumber(19)
|
||||
set memoriesList(Response_MemoriesList value) => $_setField(19, value);
|
||||
@$pb.TagNumber(19)
|
||||
$core.bool hasMemoriesList() => $_has(18);
|
||||
$core.bool hasMemoriesList() => $_has(16);
|
||||
@$pb.TagNumber(19)
|
||||
void clearMemoriesList() => $_clearField(19);
|
||||
@$pb.TagNumber(19)
|
||||
Response_MemoriesList ensureMemoriesList() => $_ensure(18);
|
||||
Response_MemoriesList ensureMemoriesList() => $_ensure(16);
|
||||
|
||||
@$pb.TagNumber(20)
|
||||
Response_MemoriesUrl get memoriesUrl => $_getN(19);
|
||||
Response_MemoriesUrl get memoriesUrl => $_getN(17);
|
||||
@$pb.TagNumber(20)
|
||||
set memoriesUrl(Response_MemoriesUrl value) => $_setField(20, value);
|
||||
@$pb.TagNumber(20)
|
||||
$core.bool hasMemoriesUrl() => $_has(19);
|
||||
$core.bool hasMemoriesUrl() => $_has(17);
|
||||
@$pb.TagNumber(20)
|
||||
void clearMemoriesUrl() => $_clearField(20);
|
||||
@$pb.TagNumber(20)
|
||||
Response_MemoriesUrl ensureMemoriesUrl() => $_ensure(19);
|
||||
Response_MemoriesUrl ensureMemoriesUrl() => $_ensure(17);
|
||||
|
||||
@$pb.TagNumber(21)
|
||||
Response_MemoriesUsage get memoriesUsage => $_getN(20);
|
||||
Response_MemoriesUsage get memoriesUsage => $_getN(18);
|
||||
@$pb.TagNumber(21)
|
||||
set memoriesUsage(Response_MemoriesUsage value) => $_setField(21, value);
|
||||
@$pb.TagNumber(21)
|
||||
$core.bool hasMemoriesUsage() => $_has(20);
|
||||
$core.bool hasMemoriesUsage() => $_has(18);
|
||||
@$pb.TagNumber(21)
|
||||
void clearMemoriesUsage() => $_clearField(21);
|
||||
@$pb.TagNumber(21)
|
||||
Response_MemoriesUsage ensureMemoriesUsage() => $_ensure(20);
|
||||
Response_MemoriesUsage ensureMemoriesUsage() => $_ensure(18);
|
||||
}
|
||||
|
||||
enum Response_Response { ok, error, notSet }
|
||||
|
|
|
|||
|
|
@ -79,6 +79,14 @@ const V0$json = {
|
|||
'9': 0,
|
||||
'10': 'RequestNewPreKeys'
|
||||
},
|
||||
{
|
||||
'1': 'RequestNewPqcPreKeys',
|
||||
'3': 8,
|
||||
'4': 1,
|
||||
'5': 8,
|
||||
'9': 0,
|
||||
'10': 'RequestNewPqcPreKeys'
|
||||
},
|
||||
{
|
||||
'1': 'error',
|
||||
'3': 6,
|
||||
|
|
@ -100,8 +108,9 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
|||
'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90'
|
||||
'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEkEKC25ld01lc3NhZ2VzGAcgASgLMh'
|
||||
'0uc2VydmVyX3RvX2NsaWVudC5OZXdNZXNzYWdlc0gAUgtuZXdNZXNzYWdlcxIuChFSZXF1ZXN0'
|
||||
'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLm'
|
||||
'Vycm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k');
|
||||
'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxI0ChRSZXF1ZXN0TmV3UHFjUH'
|
||||
'JlS2V5cxgIIAEoCEgAUhRSZXF1ZXN0TmV3UHFjUHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLmVy'
|
||||
'cm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k');
|
||||
|
||||
@$core.Deprecated('Use newMessageDescriptor instead')
|
||||
const NewMessage$json = {
|
||||
|
|
@ -167,11 +176,12 @@ const Response$json = {
|
|||
Response_AddAccountsInvite$json,
|
||||
Response_AddAccountsInvites$json,
|
||||
Response_AdditionalAccount$json,
|
||||
Response_Deprecated$json,
|
||||
Response_Transaction$json,
|
||||
Response_PlanBallance$json,
|
||||
Response_PreKey$json,
|
||||
Response_SignedPreKey$json,
|
||||
Response_PqcPreKey$json,
|
||||
Response_PqcBundle$json,
|
||||
Response_UserData$json,
|
||||
Response_UploadToken$json,
|
||||
Response_DownloadTokens$json,
|
||||
|
|
@ -306,11 +316,6 @@ const Response_AdditionalAccount$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Deprecated$json = {
|
||||
'1': 'Deprecated',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Transaction$json = {
|
||||
'1': 'Transaction',
|
||||
|
|
@ -420,6 +425,86 @@ const Response_SignedPreKey$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PqcPreKey$json = {
|
||||
'1': 'PqcPreKey',
|
||||
'2': [
|
||||
{'1': 'ecc_pre_key_id', '3': 1, '4': 1, '5': 3, '10': 'eccPreKeyId'},
|
||||
{'1': 'ecc_pre_key', '3': 2, '4': 1, '5': 12, '10': 'eccPreKey'},
|
||||
{'1': 'kyber_pre_key_id', '3': 3, '4': 1, '5': 3, '10': 'kyberPreKeyId'},
|
||||
{'1': 'kyber_pre_key', '3': 4, '4': 1, '5': 12, '10': 'kyberPreKey'},
|
||||
{
|
||||
'1': 'kyber_pre_key_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberPreKeySignature'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PqcBundle$json = {
|
||||
'1': 'PqcBundle',
|
||||
'2': [
|
||||
{
|
||||
'1': 'ecc_signed_prekey_id',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'eccSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey_signature',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_id',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'kyberSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_signature',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'prekey',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PqcPreKey',
|
||||
'9': 0,
|
||||
'10': 'prekey',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_prekey'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_UserData$json = {
|
||||
'1': 'UserData',
|
||||
|
|
@ -487,6 +572,16 @@ const Response_UserData$json = {
|
|||
'10': 'registrationId',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'pqc_bundle',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PqcBundle',
|
||||
'9': 6,
|
||||
'10': 'pqcBundle',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_username'},
|
||||
|
|
@ -495,6 +590,7 @@ const Response_UserData$json = {
|
|||
{'1': '_signed_prekey_signature'},
|
||||
{'1': '_signed_prekey_id'},
|
||||
{'1': '_registration_id'},
|
||||
{'1': '_pqc_bundle'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -687,15 +783,6 @@ const Response_Ok$json = {
|
|||
'10': 'userdata'
|
||||
},
|
||||
{'1': 'authtoken', '3': 6, '4': 1, '5': 12, '9': 0, '10': 'authtoken'},
|
||||
{
|
||||
'1': 'deprecated_7',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated7'
|
||||
},
|
||||
{
|
||||
'1': 'authenticated',
|
||||
'3': 8,
|
||||
|
|
@ -723,15 +810,6 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'planballance'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_11',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated11'
|
||||
},
|
||||
{
|
||||
'1': 'addaccountsinvites',
|
||||
'3': 12,
|
||||
|
|
@ -825,6 +903,10 @@ const Response_Ok$json = {
|
|||
'8': [
|
||||
{'1': 'Ok'},
|
||||
],
|
||||
'9': [
|
||||
{'1': 7, '2': 8},
|
||||
{'1': 11, '2': 12},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Response`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
|
|
@ -846,79 +928,89 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'cGxhbklkEh8KC2ludml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGlwKEkFkZEFjY291bnRzSW'
|
||||
'52aXRlcxJGCgdpbnZpdGVzGAEgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRB'
|
||||
'Y2NvdW50c0ludml0ZVIHaW52aXRlcxpFChFBZGRpdGlvbmFsQWNjb3VudBIXCgd1c2VyX2lkGA'
|
||||
'EgASgDUgZ1c2VySWQSFwoHcGxhbl9pZBgDIAEoCVIGcGxhbklkGgwKCkRlcHJlY2F0ZWQaDQoL'
|
||||
'VHJhbnNhY3Rpb24alwUKDFBsYW5CYWxsYW5jZRJACh11c2VkX2RhaWx5X21lZGlhX3VwbG9hZF'
|
||||
'9saW1pdBgBIAEoA1IZdXNlZERhaWx5TWVkaWFVcGxvYWRMaW1pdBI+Chx1c2VkX3VwbG9hZF9t'
|
||||
'ZWRpYV9zaXplX2xpbWl0GAIgASgDUhh1c2VkVXBsb2FkTWVkaWFTaXplTGltaXQSMwoTcGF5bW'
|
||||
'VudF9wZXJpb2RfZGF5cxgDIAEoA0gAUhFwYXltZW50UGVyaW9kRGF5c4gBARJLCiBsYXN0X3Bh'
|
||||
'eW1lbnRfZG9uZV91bml4X3RpbWVzdGFtcBgEIAEoA0gBUhxsYXN0UGF5bWVudERvbmVVbml4VG'
|
||||
'ltZXN0YW1wiAEBEkoKDHRyYW5zYWN0aW9ucxgFIAMoCzImLnNlcnZlcl90b19jbGllbnQuUmVz'
|
||||
'cG9uc2UuVHJhbnNhY3Rpb25SDHRyYW5zYWN0aW9ucxJdChNhZGRpdGlvbmFsX2FjY291bnRzGA'
|
||||
'YgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRpdGlvbmFsQWNjb3VudFISYWRk'
|
||||
'aXRpb25hbEFjY291bnRzEiYKDGF1dG9fcmVuZXdhbBgHIAEoCEgCUgthdXRvUmVuZXdhbIgBAR'
|
||||
'JCChthZGRpdGlvbmFsX2FjY291bnRfb3duZXJfaWQYCCABKANIA1IYYWRkaXRpb25hbEFjY291'
|
||||
'bnRPd25lcklkiAEBQhYKFF9wYXltZW50X3BlcmlvZF9kYXlzQiMKIV9sYXN0X3BheW1lbnRfZG'
|
||||
'9uZV91bml4X3RpbWVzdGFtcEIPCg1fYXV0b19yZW5ld2FsQh4KHF9hZGRpdGlvbmFsX2FjY291'
|
||||
'bnRfb3duZXJfaWQaMAoGUHJlS2V5Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkYAiABKAxSBn'
|
||||
'ByZWtleRqVAQoMU2lnbmVkUHJlS2V5EigKEHNpZ25lZF9wcmVrZXlfaWQYASABKANSDnNpZ25l'
|
||||
'ZFByZWtleUlkEiMKDXNpZ25lZF9wcmVrZXkYAiABKAxSDHNpZ25lZFByZWtleRI2ChdzaWduZW'
|
||||
'RfcHJla2V5X3NpZ25hdHVyZRgDIAEoDFIVc2lnbmVkUHJla2V5U2lnbmF0dXJlGvYDCghVc2Vy'
|
||||
'RGF0YRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSOwoHcHJla2V5cxgCIAMoCzIhLnNlcnZlcl'
|
||||
'90b19jbGllbnQuUmVzcG9uc2UuUHJlS2V5UgdwcmVrZXlzEh8KCHVzZXJuYW1lGAcgASgMSABS'
|
||||
'CHVzZXJuYW1liAEBEjMKE3B1YmxpY19pZGVudGl0eV9rZXkYAyABKAxIAVIRcHVibGljSWRlbn'
|
||||
'RpdHlLZXmIAQESKAoNc2lnbmVkX3ByZWtleRgEIAEoDEgCUgxzaWduZWRQcmVrZXmIAQESOwoX'
|
||||
'c2lnbmVkX3ByZWtleV9zaWduYXR1cmUYBSABKAxIA1IVc2lnbmVkUHJla2V5U2lnbmF0dXJliA'
|
||||
'EBEi0KEHNpZ25lZF9wcmVrZXlfaWQYBiABKANIBFIOc2lnbmVkUHJla2V5SWSIAQESLAoPcmVn'
|
||||
'aXN0cmF0aW9uX2lkGAggASgDSAVSDnJlZ2lzdHJhdGlvbklkiAEBQgsKCV91c2VybmFtZUIWCh'
|
||||
'RfcHVibGljX2lkZW50aXR5X2tleUIQCg5fc2lnbmVkX3ByZWtleUIaChhfc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmVCEwoRX3NpZ25lZF9wcmVrZXlfaWRCEgoQX3JlZ2lzdHJhdGlvbl9pZBpZCg'
|
||||
'tVcGxvYWRUb2tlbhIhCgx1cGxvYWRfdG9rZW4YASABKAxSC3VwbG9hZFRva2VuEicKD2Rvd25s'
|
||||
'b2FkX3Rva2VucxgCIAMoDFIOZG93bmxvYWRUb2tlbnMaOQoORG93bmxvYWRUb2tlbnMSJwoPZG'
|
||||
'93bmxvYWRfdG9rZW5zGAEgAygMUg5kb3dubG9hZFRva2VucxpFCgtQcm9vZk9mV29yaxIWCgZw'
|
||||
'cmVmaXgYASABKAlSBnByZWZpeBIeCgpkaWZmaWN1bHR5GAIgASgDUgpkaWZmaWN1bHR5Gl4KH1'
|
||||
'Bhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2USDgoCaWQYASABKANSAmlkEisKEWVuY3J5'
|
||||
'cHRlZF9tZXNzYWdlGAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdlGnoKIFBhc3N3b3JkbGVzc05vdG'
|
||||
'lmaWNhdGlvbk1lc3NhZ2VzElYKCG1lc3NhZ2VzGAEgAygLMjouc2VydmVyX3RvX2NsaWVudC5S'
|
||||
'ZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlUghtZXNzYWdlcxqqAQoNUH'
|
||||
'Jlc2lnbmVkUG9zdBIQCgN1cmwYASABKAlSA3VybBJMCgZmaWVsZHMYAiADKAsyNC5zZXJ2ZXJf'
|
||||
'dG9fY2xpZW50LlJlc3BvbnNlLlByZXNpZ25lZFBvc3QuRmllbGRzRW50cnlSBmZpZWxkcxo5Cg'
|
||||
'tGaWVsZHNFbnRyeRIQCgNrZXkYASABKAlSA2tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWU6AjgB'
|
||||
'Gs8BChJNZW1vcmllc1VwbG9hZFVybHMSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQSUwoQdG'
|
||||
'h1bWJuYWlsX3VwbG9hZBgCIAEoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJlc2ln'
|
||||
'bmVkUG9zdFIPdGh1bWJuYWlsVXBsb2FkEkkKC2Z1bGxfdXBsb2FkGAMgASgLMiguc2VydmVyX3'
|
||||
'RvX2NsaWVudC5SZXNwb25zZS5QcmVzaWduZWRQb3N0UgpmdWxsVXBsb2FkGoEBCglNZWRpYUl0'
|
||||
'ZW0SGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3'
|
||||
'JpZ2luYWxEYXRlEjQKFnRodW1ibmFpbF9kb3dubG9hZF91cmwYAyABKAlSFHRodW1ibmFpbERv'
|
||||
'd25sb2FkVXJsGkoKDE1lbW9yaWVzTGlzdBI6CgVpdGVtcxgBIAMoCzIkLnNlcnZlcl90b19jbG'
|
||||
'llbnQuUmVzcG9uc2UuTWVkaWFJdGVtUgVpdGVtcxo5CgtNZW1vcmllc1VybBIqChFmdWxsX2Rv'
|
||||
'd25sb2FkX3VybBgBIAEoCVIPZnVsbERvd25sb2FkVXJsGmcKDU1lbW9yaWVzVXNhZ2USGwoJbW'
|
||||
'F4X2J5dGVzGAEgASgDUghtYXhCeXRlcxIjCg1jdXJyZW50X2J5dGVzGAIgASgDUgxjdXJyZW50'
|
||||
'Qnl0ZXMSFAoFY291bnQYAyABKANSBWNvdW50GoMMCgJPaxIUCgROb25lGAEgASgISABSBE5vbm'
|
||||
'USGAoGdXNlcmlkGAIgASgDSABSBnVzZXJpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1'
|
||||
'dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9rZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3'
|
||||
'BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2Vy'
|
||||
'dmVyX3RvX2NsaWVudC5SZXNwb25zZS5Vc2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW'
|
||||
'4YBiABKAxIAFIJYXV0aHRva2VuEkoKDGRlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19j'
|
||||
'bGllbnQuUmVzcG9uc2UuRGVwcmVjYXRlZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdG'
|
||||
'VkGAggASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1'
|
||||
'dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSABKAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLl'
|
||||
'BsYW5zSABSBXBsYW5zEk0KDHBsYW5iYWxsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQu'
|
||||
'UmVzcG9uc2UuUGxhbkJhbGxhbmNlSABSDHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGA'
|
||||
'sgASgLMiUuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0'
|
||||
'ZWQxMRJfChJhZGRhY2NvdW50c2ludml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3'
|
||||
'BvbnNlLkFkZEFjY291bnRzSW52aXRlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxv'
|
||||
'YWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW'
|
||||
'5zSABSDmRvd25sb2FkdG9rZW5zEk0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19j'
|
||||
'bGllbnQuUmVzcG9uc2UuU2lnbmVkUHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV2'
|
||||
'9yaxgPIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJv'
|
||||
'b2ZPZldvcmsSSQogcGFzc3dvcmRsZXNzX3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcG'
|
||||
'Fzc3dvcmRsZXNzUmVjb3ZlcnlTZXJ2ZXJLZXkSiwEKInBhc3N3b3JkbGVzc19ub3RpZmljYXRp'
|
||||
'b25fbWVzc2FnZXMYESABKAsyOy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBhc3N3b3JkbG'
|
||||
'Vzc05vdGlmaWNhdGlvbk1lc3NhZ2VzSABSIHBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3Nh'
|
||||
'Z2VzEmEKFG1lbW9yaWVzX3VwbG9hZF91cmxzGBIgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZX'
|
||||
'Nwb25zZS5NZW1vcmllc1VwbG9hZFVybHNIAFISbWVtb3JpZXNVcGxvYWRVcmxzEk4KDW1lbW9y'
|
||||
'aWVzX2xpc3QYEyABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzTGlzdE'
|
||||
'gAUgxtZW1vcmllc0xpc3QSSwoMbWVtb3JpZXNfdXJsGBQgASgLMiYuc2VydmVyX3RvX2NsaWVu'
|
||||
'dC5SZXNwb25zZS5NZW1vcmllc1VybEgAUgttZW1vcmllc1VybBJRCg5tZW1vcmllc191c2FnZR'
|
||||
'gVIAEoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuTWVtb3JpZXNVc2FnZUgAUg1tZW1v'
|
||||
'cmllc1VzYWdlQgQKAk9rQgoKCFJlc3BvbnNl');
|
||||
'EgASgDUgZ1c2VySWQSFwoHcGxhbl9pZBgDIAEoCVIGcGxhbklkGg0KC1RyYW5zYWN0aW9uGpcF'
|
||||
'CgxQbGFuQmFsbGFuY2USQAoddXNlZF9kYWlseV9tZWRpYV91cGxvYWRfbGltaXQYASABKANSGX'
|
||||
'VzZWREYWlseU1lZGlhVXBsb2FkTGltaXQSPgocdXNlZF91cGxvYWRfbWVkaWFfc2l6ZV9saW1p'
|
||||
'dBgCIAEoA1IYdXNlZFVwbG9hZE1lZGlhU2l6ZUxpbWl0EjMKE3BheW1lbnRfcGVyaW9kX2RheX'
|
||||
'MYAyABKANIAFIRcGF5bWVudFBlcmlvZERheXOIAQESSwogbGFzdF9wYXltZW50X2RvbmVfdW5p'
|
||||
'eF90aW1lc3RhbXAYBCABKANIAVIcbGFzdFBheW1lbnREb25lVW5peFRpbWVzdGFtcIgBARJKCg'
|
||||
'x0cmFuc2FjdGlvbnMYBSADKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlRyYW5zYWN0'
|
||||
'aW9uUgx0cmFuc2FjdGlvbnMSXQoTYWRkaXRpb25hbF9hY2NvdW50cxgGIAMoCzIsLnNlcnZlcl'
|
||||
'90b19jbGllbnQuUmVzcG9uc2UuQWRkaXRpb25hbEFjY291bnRSEmFkZGl0aW9uYWxBY2NvdW50'
|
||||
'cxImCgxhdXRvX3JlbmV3YWwYByABKAhIAlILYXV0b1JlbmV3YWyIAQESQgobYWRkaXRpb25hbF'
|
||||
'9hY2NvdW50X293bmVyX2lkGAggASgDSANSGGFkZGl0aW9uYWxBY2NvdW50T3duZXJJZIgBAUIW'
|
||||
'ChRfcGF5bWVudF9wZXJpb2RfZGF5c0IjCiFfbGFzdF9wYXltZW50X2RvbmVfdW5peF90aW1lc3'
|
||||
'RhbXBCDwoNX2F1dG9fcmVuZXdhbEIeChxfYWRkaXRpb25hbF9hY2NvdW50X293bmVyX2lkGjAK'
|
||||
'BlByZUtleRIOCgJpZBgBIAEoA1ICaWQSFgoGcHJla2V5GAIgASgMUgZwcmVrZXkalQEKDFNpZ2'
|
||||
'5lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1z'
|
||||
'aWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYX'
|
||||
'R1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5EiMKDmVjY19w'
|
||||
'cmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEoDFIJZWNjUH'
|
||||
'JlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIgoNa3liZXJf'
|
||||
'cHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWduYXR1cmUYBS'
|
||||
'ABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGp0DCglQcWNCdW5kbGUSLwoUZWNjX3NpZ25lZF9w'
|
||||
'cmVrZXlfaWQYASABKANSEWVjY1NpZ25lZFByZWtleUlkEioKEWVjY19zaWduZWRfcHJla2V5GA'
|
||||
'IgASgMUg9lY2NTaWduZWRQcmVrZXkSPQobZWNjX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMg'
|
||||
'ASgMUhhlY2NTaWduZWRQcmVrZXlTaWduYXR1cmUSMwoWa3liZXJfc2lnbmVkX3ByZWtleV9pZB'
|
||||
'gEIAEoA1ITa3liZXJTaWduZWRQcmVrZXlJZBIuChNreWJlcl9zaWduZWRfcHJla2V5GAUgASgM'
|
||||
'UhFreWJlclNpZ25lZFByZWtleRJBCh1reWJlcl9zaWduZWRfcHJla2V5X3NpZ25hdHVyZRgGIA'
|
||||
'EoDFIaa3liZXJTaWduZWRQcmVrZXlTaWduYXR1cmUSQQoGcHJla2V5GAcgASgLMiQuc2VydmVy'
|
||||
'X3RvX2NsaWVudC5SZXNwb25zZS5QcWNQcmVLZXlIAFIGcHJla2V5iAEBQgkKB19wcmVrZXkazw'
|
||||
'QKCFVzZXJEYXRhEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBI7CgdwcmVrZXlzGAIgAygLMiEu'
|
||||
'c2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QcmVLZXlSB3ByZWtleXMSHwoIdXNlcm5hbWUYBy'
|
||||
'ABKAxIAFIIdXNlcm5hbWWIAQESMwoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDEgBUhFwdWJs'
|
||||
'aWNJZGVudGl0eUtleYgBARIoCg1zaWduZWRfcHJla2V5GAQgASgMSAJSDHNpZ25lZFByZWtleY'
|
||||
'gBARI7ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZRgFIAEoDEgDUhVzaWduZWRQcmVrZXlTaWdu'
|
||||
'YXR1cmWIAQESLQoQc2lnbmVkX3ByZWtleV9pZBgGIAEoA0gEUg5zaWduZWRQcmVrZXlJZIgBAR'
|
||||
'IsCg9yZWdpc3RyYXRpb25faWQYCCABKANIBVIOcmVnaXN0cmF0aW9uSWSIAQESSAoKcHFjX2J1'
|
||||
'bmRsZRgJIAEoCzIkLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHFjQnVuZGxlSAZSCXBxY0'
|
||||
'J1bmRsZYgBAUILCglfdXNlcm5hbWVCFgoUX3B1YmxpY19pZGVudGl0eV9rZXlCEAoOX3NpZ25l'
|
||||
'ZF9wcmVrZXlCGgoYX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlQhMKEV9zaWduZWRfcHJla2V5X2'
|
||||
'lkQhIKEF9yZWdpc3RyYXRpb25faWRCDQoLX3BxY19idW5kbGUaWQoLVXBsb2FkVG9rZW4SIQoM'
|
||||
'dXBsb2FkX3Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhInCg9kb3dubG9hZF90b2tlbnMYAiADKA'
|
||||
'xSDmRvd25sb2FkVG9rZW5zGjkKDkRvd25sb2FkVG9rZW5zEicKD2Rvd25sb2FkX3Rva2VucxgB'
|
||||
'IAMoDFIOZG93bmxvYWRUb2tlbnMaRQoLUHJvb2ZPZldvcmsSFgoGcHJlZml4GAEgASgJUgZwcm'
|
||||
'VmaXgSHgoKZGlmZmljdWx0eRgCIAEoA1IKZGlmZmljdWx0eRpeCh9QYXNzd29yZGxlc3NOb3Rp'
|
||||
'ZmljYXRpb25NZXNzYWdlEg4KAmlkGAEgASgDUgJpZBIrChFlbmNyeXB0ZWRfbWVzc2FnZRgCIA'
|
||||
'EoDFIQZW5jcnlwdGVkTWVzc2FnZRp6CiBQYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdl'
|
||||
'cxJWCghtZXNzYWdlcxgBIAMoCzI6LnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGFzc3dvcm'
|
||||
'RsZXNzTm90aWZpY2F0aW9uTWVzc2FnZVIIbWVzc2FnZXMaqgEKDVByZXNpZ25lZFBvc3QSEAoD'
|
||||
'dXJsGAEgASgJUgN1cmwSTAoGZmllbGRzGAIgAygLMjQuc2VydmVyX3RvX2NsaWVudC5SZXNwb2'
|
||||
'5zZS5QcmVzaWduZWRQb3N0LkZpZWxkc0VudHJ5UgZmaWVsZHMaOQoLRmllbGRzRW50cnkSEAoD'
|
||||
'a2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4ARrPAQoSTWVtb3JpZXNVcG'
|
||||
'xvYWRVcmxzEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkElMKEHRodW1ibmFpbF91cGxvYWQY'
|
||||
'AiABKAsyKC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlByZXNpZ25lZFBvc3RSD3RodW1ibm'
|
||||
'FpbFVwbG9hZBJJCgtmdWxsX3VwbG9hZBgDIAEoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9u'
|
||||
'c2UuUHJlc2lnbmVkUG9zdFIKZnVsbFVwbG9hZBqBAQoJTWVkaWFJdGVtEhkKCG1lZGlhX2lkGA'
|
||||
'EgASgJUgdtZWRpYUlkEiMKDW9yaWdpbmFsX2RhdGUYAiABKANSDG9yaWdpbmFsRGF0ZRI0ChZ0'
|
||||
'aHVtYm5haWxfZG93bmxvYWRfdXJsGAMgASgJUhR0aHVtYm5haWxEb3dubG9hZFVybBpKCgxNZW'
|
||||
'1vcmllc0xpc3QSOgoFaXRlbXMYASADKAsyJC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1l'
|
||||
'ZGlhSXRlbVIFaXRlbXMaOQoLTWVtb3JpZXNVcmwSKgoRZnVsbF9kb3dubG9hZF91cmwYASABKA'
|
||||
'lSD2Z1bGxEb3dubG9hZFVybBpnCg1NZW1vcmllc1VzYWdlEhsKCW1heF9ieXRlcxgBIAEoA1II'
|
||||
'bWF4Qnl0ZXMSIwoNY3VycmVudF9ieXRlcxgCIAEoA1IMY3VycmVudEJ5dGVzEhQKBWNvdW50GA'
|
||||
'MgASgDUgVjb3VudBr1CgoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBnVzZXJpZBgCIAEo'
|
||||
'A0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hhbGxlbmdlEkoKC3'
|
||||
'VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5VcGxvYWRUb2tl'
|
||||
'bkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUm'
|
||||
'VzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgASgMSABSCWF1dGh0'
|
||||
'b2tlbhJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS'
|
||||
'5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSABKAsyIC5zZXJ2ZXJf'
|
||||
'dG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYWxsYW5jZRgKIAEoCz'
|
||||
'InLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABSDHBsYW5iYWxsYW5j'
|
||||
'ZRJfChJhZGRhY2NvdW50c2ludml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3Bvbn'
|
||||
'NlLkFkZEFjY291bnRzSW52aXRlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0'
|
||||
'b2tlbnMYDSABKAsyKS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSA'
|
||||
'BSDmRvd25sb2FkdG9rZW5zEk0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGll'
|
||||
'bnQuUmVzcG9uc2UuU2lnbmVkUHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yax'
|
||||
'gPIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZP'
|
||||
'ZldvcmsSSQogcGFzc3dvcmRsZXNzX3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcGFzc3'
|
||||
'dvcmRsZXNzUmVjb3ZlcnlTZXJ2ZXJLZXkSiwEKInBhc3N3b3JkbGVzc19ub3RpZmljYXRpb25f'
|
||||
'bWVzc2FnZXMYESABKAsyOy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBhc3N3b3JkbGVzc0'
|
||||
'5vdGlmaWNhdGlvbk1lc3NhZ2VzSABSIHBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2Vz'
|
||||
'EmEKFG1lbW9yaWVzX3VwbG9hZF91cmxzGBIgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZXNwb2'
|
||||
'5zZS5NZW1vcmllc1VwbG9hZFVybHNIAFISbWVtb3JpZXNVcGxvYWRVcmxzEk4KDW1lbW9yaWVz'
|
||||
'X2xpc3QYEyABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzTGlzdEgAUg'
|
||||
'xtZW1vcmllc0xpc3QSSwoMbWVtb3JpZXNfdXJsGBQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5S'
|
||||
'ZXNwb25zZS5NZW1vcmllc1VybEgAUgttZW1vcmllc1VybBJRCg5tZW1vcmllc191c2FnZRgVIA'
|
||||
'EoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuTWVtb3JpZXNVc2FnZUgAUg1tZW1vcmll'
|
||||
'c1VzYWdlQgQKAk9rSgQIBxAISgQICxAMQgoKCFJlc3BvbnNl');
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ class Message_Type extends $pb.ProtobufEnum {
|
|||
Message_Type._(3, _omitEnumNames ? '' : 'PREKEY_BUNDLE');
|
||||
static const Message_Type TEST_NOTIFICATION =
|
||||
Message_Type._(4, _omitEnumNames ? '' : 'TEST_NOTIFICATION');
|
||||
static const Message_Type CIPHERTEXT_V2 =
|
||||
Message_Type._(5, _omitEnumNames ? '' : 'CIPHERTEXT_V2');
|
||||
|
||||
static const $core.List<Message_Type> values = <Message_Type>[
|
||||
SENDER_DELIVERY_RECEIPT,
|
||||
|
|
@ -32,10 +34,11 @@ class Message_Type extends $pb.ProtobufEnum {
|
|||
CIPHERTEXT,
|
||||
PREKEY_BUNDLE,
|
||||
TEST_NOTIFICATION,
|
||||
CIPHERTEXT_V2,
|
||||
];
|
||||
|
||||
static final $core.List<Message_Type?> _byValue =
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 4);
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 5);
|
||||
static Message_Type? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const Message_Type$json = {
|
|||
{'1': 'CIPHERTEXT', '2': 2},
|
||||
{'1': 'PREKEY_BUNDLE', '2': 3},
|
||||
{'1': 'TEST_NOTIFICATION', '2': 4},
|
||||
{'1': 'CIPHERTEXT_V2', '2': 5},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -65,10 +66,10 @@ final $typed_data.Uint8List messageDescriptor = $convert.base64Decode(
|
|||
'CgdNZXNzYWdlEiEKBHR5cGUYASABKA4yDS5NZXNzYWdlLlR5cGVSBHR5cGUSHQoKcmVjZWlwdF'
|
||||
'9pZBgCIAEoCVIJcmVjZWlwdElkEjAKEWVuY3J5cHRlZF9jb250ZW50GAMgASgMSABSEGVuY3J5'
|
||||
'cHRlZENvbnRlbnSIAQESQwoRcGxhaW50ZXh0X2NvbnRlbnQYBCABKAsyES5QbGFpbnRleHRDb2'
|
||||
'50ZW50SAFSEHBsYWludGV4dENvbnRlbnSIAQEidAoEVHlwZRIbChdTRU5ERVJfREVMSVZFUllf'
|
||||
'UkVDRUlQVBAAEhUKEVBMQUlOVEVYVF9DT05URU5UEAESDgoKQ0lQSEVSVEVYVBACEhEKDVBSRU'
|
||||
'tFWV9CVU5ETEUQAxIVChFURVNUX05PVElGSUNBVElPThAEQhQKEl9lbmNyeXB0ZWRfY29udGVu'
|
||||
'dEIUChJfcGxhaW50ZXh0X2NvbnRlbnQ=');
|
||||
'50ZW50SAFSEHBsYWludGV4dENvbnRlbnSIAQEihwEKBFR5cGUSGwoXU0VOREVSX0RFTElWRVJZ'
|
||||
'X1JFQ0VJUFQQABIVChFQTEFJTlRFWFRfQ09OVEVOVBABEg4KCkNJUEhFUlRFWFQQAhIRCg1QUk'
|
||||
'VLRVlfQlVORExFEAMSFQoRVEVTVF9OT1RJRklDQVRJT04QBBIRCg1DSVBIRVJURVhUX1YyEAVC'
|
||||
'FAoSX2VuY3J5cHRlZF9jb250ZW50QhQKEl9wbGFpbnRleHRfY29udGVudA==');
|
||||
|
||||
@$core.Deprecated('Use plaintextContentDescriptor instead')
|
||||
const PlaintextContent$json = {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ message Message {
|
|||
CIPHERTEXT = 2;
|
||||
PREKEY_BUNDLE = 3;
|
||||
TEST_NOTIFICATION = 4;
|
||||
CIPHERTEXT_V2 = 5;
|
||||
}
|
||||
Type type = 1;
|
||||
string receipt_id = 2;
|
||||
|
|
|
|||
|
|
@ -2,18 +2,23 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
late ThemeMode _themeMode;
|
||||
late Color _primaryColor;
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
Color get primaryColor => _primaryColor;
|
||||
|
||||
void loadSettings() {
|
||||
if (userService.isUserCreated) {
|
||||
_themeMode = userService.currentUser.themeMode;
|
||||
_primaryColor = userService.currentUser.primaryColor;
|
||||
notifyListeners();
|
||||
} else {
|
||||
_themeMode = ThemeMode.system;
|
||||
_primaryColor = defaultPrimaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -28,4 +33,16 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|||
|
||||
await UserService.update((u) => u.themeMode = newThemeMode);
|
||||
}
|
||||
|
||||
Future<void> updatePrimaryColor(Color newColor) async {
|
||||
if (newColor.toARGB32() == _primaryColor.toARGB32()) return;
|
||||
|
||||
_primaryColor = newColor;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
await UserService.update(
|
||||
(u) => u.primaryColorValue = newColor.toARGB32(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
||||
as client;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
|
|
@ -625,7 +627,13 @@ class ApiService {
|
|||
|
||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
||||
|
||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
||||
final signedPreKeysList = await signalStore.loadSignedPreKeys();
|
||||
if (signedPreKeysList.isEmpty) {
|
||||
throw Exception(
|
||||
'Signal Signed PreKeys list is empty. Database insertion likely failed due to lack of storage space or a corrupted database.',
|
||||
);
|
||||
}
|
||||
final signedPreKey = signedPreKeysList[0];
|
||||
|
||||
final loginToken = await RustKeyManager.getLoginToken();
|
||||
|
||||
|
|
@ -992,6 +1000,28 @@ class ApiService {
|
|||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> uploadPqcPreKeys(
|
||||
int eccSignedPreKeyId,
|
||||
Uint8List eccSignedPreKey,
|
||||
Uint8List eccSignedPreKeySignature,
|
||||
int kyberSignedPreKeyId,
|
||||
Uint8List kyberSignedPreKey,
|
||||
Uint8List kyberSignedPreKeySignature,
|
||||
List<client.ApplicationData_PqcPreKey> prekeys,
|
||||
) async {
|
||||
final get = ApplicationData_UploadPqcPreKeys()
|
||||
..eccSignedPrekeyId = Int64(eccSignedPreKeyId)
|
||||
..eccSignedPrekey = eccSignedPreKey
|
||||
..eccSignedPrekeySignature = eccSignedPreKeySignature
|
||||
..kyberSignedPrekeyId = Int64(kyberSignedPreKeyId)
|
||||
..kyberSignedPrekey = kyberSignedPreKey
|
||||
..kyberSignedPrekeySignature = kyberSignedPreKeySignature
|
||||
..prekeys.addAll(prekeys);
|
||||
final appData = ApplicationData()..uploadPqcPrekeys = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
|
||||
final ballance = await getPlanBallance();
|
||||
if (ballance != null) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
||||
as client;
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
|
|
@ -18,3 +19,25 @@ Future<client.Response> handleRequestNewPreKey() async {
|
|||
final ok = client.Response_Ok()..prekeys = prekeys;
|
||||
return client.Response()..ok = ok;
|
||||
}
|
||||
|
||||
Future<client.Response?> handleRequestNewPqcPreKey() async {
|
||||
final pqcKeys = await RustSignal.generatePqcPrekeys();
|
||||
if (pqcKeys.isEmpty) return null;
|
||||
|
||||
final prekeysList = <client.ApplicationData_PqcPreKey>[];
|
||||
for (final pqcKey in pqcKeys) {
|
||||
prekeysList.add(
|
||||
client.ApplicationData_PqcPreKey(
|
||||
eccPreKeyId: Int64(pqcKey.eccPreKeyId),
|
||||
eccPreKey: pqcKey.eccPreKey,
|
||||
kyberPreKeyId: Int64(pqcKey.kyberPreKeyId),
|
||||
kyberPreKey: pqcKey.kyberPreKey,
|
||||
kyberPreKeySignature: pqcKey.kyberPreKeySignature,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final prekeys = client.Response_PqcPrekeys(prekeys: prekeysList);
|
||||
final ok = client.Response_Ok()..prekeysPqc = prekeys;
|
||||
return client.Response()..ok = ok;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,6 +392,7 @@ Future<void> insertMediaFileInMessagesTable(
|
|||
List<String> groupIds, {
|
||||
AdditionalMessageData? additionalData,
|
||||
}) async {
|
||||
await twonlyDB.transaction(() async {
|
||||
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
||||
const MediaFilesCompanion(
|
||||
isDraftMedia: Value(false),
|
||||
|
|
@ -418,7 +419,10 @@ Future<void> insertMediaFileInMessagesTable(
|
|||
),
|
||||
),
|
||||
);
|
||||
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||
await twonlyDB.groupsDao.increaseLastMessageExchange(
|
||||
groupId,
|
||||
clock.now(),
|
||||
);
|
||||
if (message != null) {
|
||||
Log.info(
|
||||
'Created message ${message.messageId} for media ${message.mediaId}',
|
||||
|
|
@ -435,6 +439,7 @@ Future<void> insertMediaFileInMessagesTable(
|
|||
Log.error('Error inserting media upload message in database.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
unawaited(startBackgroundMediaUpload(mediaService));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import 'dart:io';
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
|
|
@ -176,11 +176,11 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
}
|
||||
|
||||
if (message.type == pb.Message_Type.CIPHERTEXT) {
|
||||
final cipherText = await signalEncryptMessage(
|
||||
final encryptResult = await signalEncryptMessage(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (cipherText == null) {
|
||||
if (encryptResult == null) {
|
||||
Log.error(
|
||||
'[${receipt.receiptId}] Could not encrypt the message for user ${receipt.contactId}. Aborting and trying again.',
|
||||
);
|
||||
|
|
@ -194,16 +194,31 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||
return null;
|
||||
}
|
||||
message.encryptedContent = cipherText.serialize();
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
message.type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
message.type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
message
|
||||
..encryptedContent = encryptResult.ciphertext
|
||||
..type = encryptResult.type;
|
||||
} else if (message.type == pb.Message_Type.CIPHERTEXT_V2) {
|
||||
final encryptResult = await signalEncryptMessageV2(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (encryptResult == null) {
|
||||
Log.error(
|
||||
'[${receipt.receiptId}] Could not encrypt the message (V2) for user ${receipt.contactId}. Aborting and trying again.',
|
||||
);
|
||||
if (receipt.messageId != null) {
|
||||
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||
receipt.contactId,
|
||||
receipt.messageId!,
|
||||
clock.now(),
|
||||
);
|
||||
}
|
||||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||
return null;
|
||||
}
|
||||
message
|
||||
..encryptedContent = encryptResult.ciphertext
|
||||
..type = encryptResult.type;
|
||||
}
|
||||
|
||||
if (onlyReturnEncryptedData) {
|
||||
|
|
@ -482,8 +497,11 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
|||
}
|
||||
}
|
||||
|
||||
final contact = await twonlyDB.contactsDao.getContactById(contactId);
|
||||
final isV2 = contact?.signalVersion == SignalVersion.v2;
|
||||
|
||||
final response = pb.Message()
|
||||
..type = pb.Message_Type.CIPHERTEXT
|
||||
..type = isV2 ? pb.Message_Type.CIPHERTEXT_V2 : pb.Message_Type.CIPHERTEXT
|
||||
..encryptedContent = encryptedContent.writeToBuffer();
|
||||
|
||||
var retryCounter = 0;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
|||
try {
|
||||
if (msg.v0.hasRequestNewPreKeys()) {
|
||||
response = await handleRequestNewPreKey();
|
||||
} else if (msg.v0.hasRequestNewPqcPreKeys()) {
|
||||
response = (await handleRequestNewPqcPreKey()) ?? response;
|
||||
} else if (msg.v0.hasNewMessage()) {
|
||||
Log.info('Got 1 message from the server.');
|
||||
await handleClient2ClientMessage(msg.v0.newMessage);
|
||||
|
|
@ -190,6 +192,7 @@ Future<void> _handleClient2ClientMessage(
|
|||
}
|
||||
|
||||
case Message_Type.CIPHERTEXT:
|
||||
case Message_Type.CIPHERTEXT_V2:
|
||||
case Message_Type.PREKEY_BUNDLE:
|
||||
if (message.hasEncryptedContent()) {
|
||||
Value<String>? receiptIdDB;
|
||||
|
|
@ -297,12 +300,23 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
|||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
Log.info('[$receiptId] calling signalDecryptMessage');
|
||||
var (encryptedContent, decryptionErrorType) = await signalDecryptMessage(
|
||||
EncryptedContent? encryptedContent;
|
||||
PlaintextContent_DecryptionErrorMessage_Type? decryptionErrorType;
|
||||
|
||||
if (messageType == Message_Type.CIPHERTEXT_V2) {
|
||||
(encryptedContent, decryptionErrorType) = await signalDecryptMessageV2(
|
||||
fromUserId,
|
||||
encryptedContentRaw,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
} else {
|
||||
(encryptedContent, decryptionErrorType) = await signalDecryptMessageV1(
|
||||
fromUserId,
|
||||
encryptedContentRaw,
|
||||
messageType.value,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
}
|
||||
|
||||
if (encryptedContent == null) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -33,7 +33,12 @@ class Result<T, E> {
|
|||
}
|
||||
|
||||
DateTime fromTimestamp(Int64 timeStamp) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(timeStamp.toInt());
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(timeStamp.toInt());
|
||||
final now = DateTime.now();
|
||||
if (date.isAfter(now)) {
|
||||
return now;
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
// ignore: strict_raw_type
|
||||
|
|
|
|||
|
|
@ -89,7 +89,10 @@ Future<bool> initBackgroundExecution() async {
|
|||
final Mutex _keyValueMutex = Mutex();
|
||||
|
||||
// ignore: unreachable_from_main
|
||||
Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
||||
Future<bool> backgroundFetch({
|
||||
int? lastExecutionInSecondsLimit = 120,
|
||||
}) async {
|
||||
if (lastExecutionInSecondsLimit != null) {
|
||||
final shouldBeExecuted = await exclusiveAccess(
|
||||
lockName: 'periodic_task',
|
||||
mutex: _keyValueMutex,
|
||||
|
|
@ -116,9 +119,10 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
|||
},
|
||||
);
|
||||
|
||||
if (!shouldBeExecuted) return;
|
||||
if (!shouldBeExecuted) return false;
|
||||
}
|
||||
|
||||
Log.info('eu.twonly.periodic_task was called.');
|
||||
Log.info('Periodic task was called.');
|
||||
AppState.gotMessageFromServer = false;
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
|
@ -130,14 +134,16 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
|||
|
||||
if (!await apiService.connect()) {
|
||||
Log.info('Could not connect to the api. Returning early.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!apiService.isAuthenticated) {
|
||||
Log.info('Api is not authenticated. Returning early.');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var receiveMessage = false;
|
||||
|
||||
try {
|
||||
while (!AppState.gotMessageFromServer) {
|
||||
if (stopwatch.elapsed.inSeconds >= 15) {
|
||||
|
|
@ -148,19 +154,22 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
|||
}
|
||||
|
||||
if (AppState.gotMessageFromServer) {
|
||||
receiveMessage = true;
|
||||
Log.info('Received a server message from the server.');
|
||||
}
|
||||
|
||||
await finishStartedPreprocessing();
|
||||
|
||||
if (lastExecutionInSecondsLimit != null) {
|
||||
await Future.delayed(const Duration(milliseconds: 2000));
|
||||
}
|
||||
} finally {
|
||||
await apiService.close(() {});
|
||||
stopwatch.stop();
|
||||
}
|
||||
|
||||
Log.info('eu.twonly.periodic_task finished after ${stopwatch.elapsed}.');
|
||||
return;
|
||||
Log.info('Periodic task finished after ${stopwatch.elapsed}.');
|
||||
return receiveMessage;
|
||||
}
|
||||
|
||||
Future<void> handleProcessingTask() async {
|
||||
|
|
|
|||
|
|
@ -323,10 +323,12 @@ class MediaFileService {
|
|||
name: mediaFile.mediaId,
|
||||
);
|
||||
} else {
|
||||
await saveImageToGallery(
|
||||
storedPath.readAsBytesSync(),
|
||||
unawaited(
|
||||
saveImageToGallery(
|
||||
await storedPath.readAsBytes(),
|
||||
createdAt: mediaFile.createdAt,
|
||||
name: mediaFile.mediaId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
|
|
@ -100,6 +102,7 @@ Future<void> showLocalPushNotification(
|
|||
PushUser pushUser,
|
||||
PushNotification pushNotification, {
|
||||
String? groupId,
|
||||
String? titleSuffix,
|
||||
}) async {
|
||||
String? title;
|
||||
String? body;
|
||||
|
|
@ -125,7 +128,9 @@ Future<void> showLocalPushNotification(
|
|||
if (targetGroupId != null) {
|
||||
try {
|
||||
final currentUri = routerProvider.routerDelegate.currentConfiguration.uri;
|
||||
if (currentUri.path.contains(targetGroupId)) {
|
||||
final isResumed =
|
||||
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed;
|
||||
if (isResumed && currentUri.path.contains(targetGroupId)) {
|
||||
Log.info(
|
||||
'Suppressing local push notification because chat with group $targetGroupId is currently open.',
|
||||
);
|
||||
|
|
@ -137,6 +142,10 @@ Future<void> showLocalPushNotification(
|
|||
}
|
||||
|
||||
title = pushUser.displayName;
|
||||
if (titleSuffix != null && titleSuffix.isNotEmpty) {
|
||||
title = '$title $titleSuffix';
|
||||
}
|
||||
|
||||
body = getPushNotificationText(pushNotification);
|
||||
if (body == '') {
|
||||
Log.error('No push notification type defined!');
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|||
|
||||
if (Platform.isAndroid) {
|
||||
if (isInitialized) {
|
||||
await handlePeriodicTask(lastExecutionInSecondsLimit: 10);
|
||||
await backgroundFetch(lastExecutionInSecondsLimit: 3);
|
||||
}
|
||||
} else {
|
||||
// make sure every thing run...
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/services/notifications/fcm.background.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
|
@ -144,6 +145,7 @@ class FcmNotificationService {
|
|||
}
|
||||
|
||||
static Future<void> handleRemoteMessage(RemoteMessage message) async {
|
||||
Log.info('handleRemoteMessage received message: ${message.messageId}');
|
||||
await _updateLastFcmMessageTimestamp();
|
||||
if (!Platform.isAndroid) {
|
||||
Log.error('Got message in Dart while on iOS');
|
||||
|
|
@ -155,6 +157,24 @@ class FcmNotificationService {
|
|||
return;
|
||||
}
|
||||
|
||||
// In scenarios like Android Doze Mode or aggressive background restrictions, the OS may kill
|
||||
// or heavily restrict network access, preventing the WebSocket from connecting in time.
|
||||
// By parsing the FCM data payload offline, we can instantly display the notification, which also
|
||||
// prevents FCM from penalizing/downgrading the app's data message priority for failing to show a notification.
|
||||
// This is just a workarround until the new Rust decryption is enrolled fully.
|
||||
final pushDataString = message.data['push_data'] as String?;
|
||||
if (pushDataString != null) {
|
||||
if (apiService.isConnected) {
|
||||
Log.info('Got FCM message, but API is connected...');
|
||||
} else {
|
||||
Log.info('Trying to connect to the API in the background.');
|
||||
|
||||
if (await backgroundFetch()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.notification != null || message.data['title'] != null) {
|
||||
final title =
|
||||
message.notification?.title ?? message.data['title'] as String? ?? '';
|
||||
|
|
|
|||
|
|
@ -3,23 +3,48 @@ import 'dart:typed_data';
|
|||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
Future<CiphertextMessage?> signalEncryptMessage(
|
||||
class SignalEncryptResult {
|
||||
SignalEncryptResult(this.ciphertext, this.type);
|
||||
final Uint8List ciphertext;
|
||||
final pb.Message_Type type;
|
||||
}
|
||||
|
||||
Future<SignalEncryptResult?> signalEncryptMessage(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
return lockingSignalProtocol.protect<CiphertextMessage?>(() async {
|
||||
return _signalEncryptMessage(target, plaintextContent);
|
||||
return lockingSignalProtocol.protect<SignalEncryptResult?>(() async {
|
||||
return _signalEncryptMessageV1(target, plaintextContent);
|
||||
});
|
||||
}
|
||||
|
||||
Future<CiphertextMessage?> _signalEncryptMessage(
|
||||
Future<SignalEncryptResult?> signalEncryptMessageV2(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
try {
|
||||
final res = await RustSignal.encrypt(
|
||||
name: target.toString(),
|
||||
deviceId: 1,
|
||||
plaintext: plaintextContent,
|
||||
);
|
||||
return SignalEncryptResult(res, pb.Message_Type.CIPHERTEXT_V2);
|
||||
} catch (e) {
|
||||
Log.error('Could not encrypt message (V2) for target $target: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SignalEncryptResult?> _signalEncryptMessageV1(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
|
|
@ -27,28 +52,93 @@ Future<CiphertextMessage?> _signalEncryptMessage(
|
|||
final signalStore = (await getSignalStore())!;
|
||||
final address = getSignalAddress(target);
|
||||
final session = SessionCipher.fromStore(signalStore, address);
|
||||
return await session.encrypt(plaintextContent);
|
||||
final cipherText = await session.encrypt(plaintextContent);
|
||||
|
||||
pb.Message_Type type;
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalEncryptResult(cipherText.serialize(), type);
|
||||
} catch (e) {
|
||||
Log.error('Could not encrypt message for target $target: $e');
|
||||
Log.error('Could not encrypt message (V1) for target $target: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||
signalDecryptMessage(
|
||||
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||
signalDecryptMessageV2(
|
||||
int fromUserId,
|
||||
Uint8List encryptedContentRaw,
|
||||
int type, {
|
||||
Uint8List encryptedContentRaw, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
// Hold the lock only for the cryptographic operation, not for network I/O
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId');
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId (V2)');
|
||||
final (
|
||||
decryptedContent,
|
||||
errorType,
|
||||
needsResync,
|
||||
) = await lockingSignalProtocol.protect(() async {
|
||||
Log.info('Lock acquired for $fromUserId');
|
||||
Log.info('Lock acquired for $fromUserId (V2)');
|
||||
try {
|
||||
final plaintext = await RustSignal.decrypt(
|
||||
name: fromUserId.toString(),
|
||||
deviceId: 1,
|
||||
ciphertext: encryptedContentRaw,
|
||||
);
|
||||
recordResyncAttempt(fromUserId, success: true);
|
||||
return (pb.EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
} catch (e) {
|
||||
Log.error('Could not decrypt message (V2) from $fromUserId: $e');
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
true, // Needs resync on failure
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Log.info('Released lockingSignalProtocol for $fromUserId (V2)');
|
||||
|
||||
if (needsResync) {
|
||||
brokenSessionsInCurrentBatch?.add(fromUserId);
|
||||
if (shouldAttemptResync(fromUserId)) {
|
||||
if (await handleSessionResync(fromUserId)) {
|
||||
recordResyncAttempt(fromUserId, success: false);
|
||||
await sendCipherText(
|
||||
fromUserId,
|
||||
pb.EncryptedContent(
|
||||
errorMessages: pb.EncryptedContent_ErrorMessages(
|
||||
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (decryptedContent, errorType);
|
||||
}
|
||||
|
||||
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||
signalDecryptMessageV1(
|
||||
int fromUserId,
|
||||
Uint8List encryptedContentRaw,
|
||||
int type, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId (V1)');
|
||||
final (
|
||||
decryptedContent,
|
||||
errorType,
|
||||
needsResync,
|
||||
) = await lockingSignalProtocol.protect(() async {
|
||||
Log.info('Lock acquired for $fromUserId (V1)');
|
||||
try {
|
||||
final session = SessionCipher.fromStore(
|
||||
(await getSignalStore())!,
|
||||
|
|
@ -70,18 +160,18 @@ signalDecryptMessage(
|
|||
Log.error('Unknown Message Decryption Type: $type');
|
||||
return (
|
||||
null,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
recordResyncAttempt(fromUserId, success: true);
|
||||
return (EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
return (pb.EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
} on InvalidKeyIdException catch (e) {
|
||||
Log.warn(e);
|
||||
return (
|
||||
null,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN,
|
||||
false,
|
||||
);
|
||||
} on DuplicateMessageException catch (e) {
|
||||
|
|
@ -91,27 +181,27 @@ signalDecryptMessage(
|
|||
Log.info(e);
|
||||
return (
|
||||
null,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
false,
|
||||
);
|
||||
} on InvalidMessageException catch (e) {
|
||||
Log.warn(e);
|
||||
return (
|
||||
null,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
true,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return (
|
||||
null,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Log.info('Released lockingSignalProtocol for $fromUserId');
|
||||
Log.info('Released lockingSignalProtocol for $fromUserId (V1)');
|
||||
|
||||
// Handle session resync OUTSIDE the lock to avoid holding it during
|
||||
// network round-trips (which can block for up to 60 seconds)
|
||||
|
|
@ -127,9 +217,9 @@ signalDecryptMessage(
|
|||
// session
|
||||
await sendCipherText(
|
||||
fromUserId,
|
||||
EncryptedContent(
|
||||
errorMessages: EncryptedContent_ErrorMessages(
|
||||
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
pb.EncryptedContent(
|
||||
errorMessages: pb.EncryptedContent_ErrorMessages(
|
||||
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
||||
|
|
@ -13,26 +15,20 @@ import 'package:twonly/src/utils/log.dart';
|
|||
|
||||
class SignalIdentityService {
|
||||
static Future<void> onAuthenticated() async {
|
||||
if (userService.currentUser.signalLastSignedPreKeyUpdated != null) {
|
||||
final fortyEightHoursAgo = clock.now().subtract(
|
||||
const Duration(hours: 48),
|
||||
);
|
||||
final isYoungerThan48Hours =
|
||||
(userService.currentUser.signalLastSignedPreKeyUpdated!).isAfter(
|
||||
final now = clock.now();
|
||||
final fortyEightHoursAgo = now.subtract(const Duration(hours: 48));
|
||||
final oneWeekAgo = now.subtract(const Duration(days: 7));
|
||||
|
||||
if (userService.currentUser.signalLastSignedPreKeyUpdated == null ||
|
||||
!userService.currentUser.signalLastSignedPreKeyUpdated!.isAfter(
|
||||
fortyEightHoursAgo,
|
||||
);
|
||||
if (isYoungerThan48Hours) {
|
||||
// The key does live for 48 hours then it expires and a new key is generated.
|
||||
return;
|
||||
}
|
||||
}
|
||||
)) {
|
||||
final signedPreKey = await _getNewSignalSignedPreKey();
|
||||
if (signedPreKey == null) {
|
||||
Log.error('could not generate a new signed pre key!');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await UserService.update((user) {
|
||||
user.signalLastSignedPreKeyUpdated = clock.now();
|
||||
user.signalLastSignedPreKeyUpdated = now;
|
||||
});
|
||||
final res = await apiService.updateSignedPreKey(
|
||||
signedPreKey.id,
|
||||
|
|
@ -48,6 +44,34 @@ class SignalIdentityService {
|
|||
Log.info('updated signed pre key');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userService.currentUser.signalLastPqcPreKeysUploaded == null ||
|
||||
!userService.currentUser.signalLastPqcPreKeysUploaded!.isAfter(
|
||||
oneWeekAgo,
|
||||
)) {
|
||||
final bundle = await RustSignal.generateBundle();
|
||||
|
||||
final pqcRes = await apiService.uploadPqcPreKeys(
|
||||
bundle.signedPreKeyId,
|
||||
bundle.signedPreKeyPublic,
|
||||
bundle.signedPreKeySignature,
|
||||
bundle.kyberPreKeyId,
|
||||
bundle.kyberPreKeyPublic,
|
||||
bundle.kyberPreKeySignature,
|
||||
[],
|
||||
);
|
||||
|
||||
if (pqcRes.isError) {
|
||||
Log.warn('could not update the pqc signed pre key: ${pqcRes.error}');
|
||||
} else {
|
||||
Log.info('updated pqc signed pre key');
|
||||
await UserService.update((user) {
|
||||
user.signalLastPqcPreKeysUploaded = now;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PreKeyRecord>> signalGetPreKeys() async {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/core/signal/engine.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
||||
import 'package:twonly/src/services/signal/consts.signal.dart';
|
||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||
|
|
@ -15,6 +20,106 @@ Future<bool> processSignalUserData(Response_UserData userData) async {
|
|||
}
|
||||
|
||||
Future<bool> _processSignalUserData(Response_UserData userData) async {
|
||||
if (userData.hasPqcBundle()) {
|
||||
return _processSignalUserDataV2(userData);
|
||||
}
|
||||
return _processSignalUserDataV1(userData);
|
||||
}
|
||||
|
||||
Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
||||
try {
|
||||
final tempIdentityKey = IdentityKey(
|
||||
Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.publicIdentityKey),
|
||||
).serialize(),
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
final signalStore = await getSignalStore();
|
||||
if (signalStore != null) {
|
||||
final existingIdentity = await signalStore.getIdentity(
|
||||
SignalProtocolAddress(userData.userId.toString(), defaultDeviceId),
|
||||
);
|
||||
|
||||
if (existingIdentity != null && existingIdentity != tempIdentityKey) {
|
||||
Log.error(
|
||||
'Identity key mismatch for contact ${userData.userId}! Existing V1 key does not match the incoming V2 identity key.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int? tempEccPreKeyId;
|
||||
Uint8List? tempEccPreKeyPublic;
|
||||
if (userData.pqcBundle.hasPrekey()) {
|
||||
tempEccPreKeyId = userData.pqcBundle.prekey.eccPreKeyId.toInt();
|
||||
tempEccPreKeyPublic = Uint8List.fromList(
|
||||
userData.pqcBundle.prekey.eccPreKey,
|
||||
);
|
||||
} else if (userData.prekeys.isNotEmpty) {
|
||||
tempEccPreKeyId = userData.prekeys.first.id.toInt();
|
||||
tempEccPreKeyPublic = Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
||||
).serialize(),
|
||||
1,
|
||||
).serialize();
|
||||
}
|
||||
|
||||
final tempKyberPreKeyId = userData.pqcBundle.hasPrekey()
|
||||
? userData.pqcBundle.prekey.kyberPreKeyId.toInt()
|
||||
: userData.pqcBundle.kyberSignedPrekeyId.toInt();
|
||||
final tempKyberPreKeyPublic = userData.pqcBundle.hasPrekey()
|
||||
? Uint8List.fromList(userData.pqcBundle.prekey.kyberPreKey)
|
||||
: Uint8List.fromList(userData.pqcBundle.kyberSignedPrekey);
|
||||
final tempKyberPreKeySignature = userData.pqcBundle.hasPrekey()
|
||||
? Uint8List.fromList(userData.pqcBundle.prekey.kyberPreKeySignature)
|
||||
: Uint8List.fromList(userData.pqcBundle.kyberSignedPrekeySignature);
|
||||
|
||||
final rustBundle = FrbPreKeyBundle(
|
||||
registrationId: userData.registrationId.toInt(),
|
||||
deviceId: 1,
|
||||
preKeyId: tempEccPreKeyId,
|
||||
preKeyPublic: tempEccPreKeyPublic,
|
||||
signedPreKeyId: userData.pqcBundle.eccSignedPrekeyId.toInt(),
|
||||
signedPreKeyPublic: Uint8List.fromList(
|
||||
userData.pqcBundle.eccSignedPrekey,
|
||||
),
|
||||
signedPreKeySignature: Uint8List.fromList(
|
||||
userData.pqcBundle.eccSignedPrekeySignature,
|
||||
),
|
||||
identityKey: tempIdentityKey.publicKey.serialize(),
|
||||
kyberPreKeyId: tempKyberPreKeyId,
|
||||
kyberPreKeyPublic: tempKyberPreKeyPublic,
|
||||
kyberPreKeySignature: tempKyberPreKeySignature,
|
||||
);
|
||||
|
||||
await RustSignal.processPrekeyBundle(
|
||||
name: userData.userId.toString(),
|
||||
deviceId: 1,
|
||||
bundle: rustBundle,
|
||||
);
|
||||
|
||||
final contact = await twonlyDB.contactsDao.getContactById(
|
||||
userData.userId.toInt(),
|
||||
);
|
||||
if (contact != null && contact.signalVersion != SignalVersion.v2) {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
const ContactsCompanion(signalVersion: drift.Value(SignalVersion.v2)),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error('could not process pqc bundle: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _processSignalUserDataV1(Response_UserData userData) async {
|
||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||
|
||||
if (signalStore == null) {
|
||||
|
|
|
|||
|
|
@ -210,6 +210,10 @@ Future<bool> authenticateUser(
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Catch unexpected platform exceptions (e.g. PlatformException) that
|
||||
// would otherwise propagate and leave callers in a broken state.
|
||||
Log.error('Unexpected authentication error: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -260,6 +264,28 @@ String formatDateTime(BuildContext context, DateTime? dateTime) {
|
|||
}
|
||||
}
|
||||
|
||||
String formatRelativeDateTime(BuildContext context, DateTime? dateTime) {
|
||||
if (dateTime == null) {
|
||||
return '-';
|
||||
}
|
||||
final now = clock.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
final dateDay = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||
|
||||
final time = DateFormat.Hm(
|
||||
Localizations.localeOf(context).toLanguageTag(),
|
||||
).format(dateTime);
|
||||
|
||||
if (dateDay == today) {
|
||||
return context.lang.todayAt(time);
|
||||
} else if (dateDay == yesterday) {
|
||||
return context.lang.yesterdayAt(time);
|
||||
} else {
|
||||
return formatDateTime(context, dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
String formatBytes(int bytes) {
|
||||
if (bytes <= 0) return '0 Bytes';
|
||||
const units = <String>['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
|
|
|||
|
|
@ -1,11 +1,27 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/secure_storage.dart';
|
||||
|
||||
Future<bool> deleteLocalUserData() async {
|
||||
await twonlyDB.close();
|
||||
// Wait for the background drift isolate to potentially shut down
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
// Remove port mappings to prevent connecting to an old, detached isolate that holds a deleted DB file.
|
||||
IsolateNameServer.removePortNameMapping('drift-db/twonly');
|
||||
IsolateNameServer.removePortNameMapping('drift-db/twonly/control');
|
||||
|
||||
final appDir = await getApplicationSupportDirectory();
|
||||
if (appDir.existsSync()) {
|
||||
appDir.deleteSync(recursive: true);
|
||||
}
|
||||
await SecureStorage.instance.deleteAll();
|
||||
locator
|
||||
..unregister<TwonlyDB>()
|
||||
..registerLazySingleton<TwonlyDB>(TwonlyDB.new);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
144
lib/src/visual/components/contact_labels.comp.dart
Normal file
144
lib/src/visual/components/contact_labels.comp.dart
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class ContactLabels extends StatefulWidget {
|
||||
const ContactLabels({
|
||||
required this.contactId,
|
||||
this.fontSize = 8,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
this.emptyText,
|
||||
this.showEmptyText = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int contactId;
|
||||
final double fontSize;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final String? emptyText;
|
||||
final bool showEmptyText;
|
||||
|
||||
@override
|
||||
State<ContactLabels> createState() => _ContactLabelsState();
|
||||
}
|
||||
|
||||
class _ContactLabelsState extends State<ContactLabels> {
|
||||
List<Label> _labels = [];
|
||||
late StreamSubscription<List<Label>> _sub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_sub = twonlyDB.labelsDao.watchContactLabels(widget.contactId).listen((
|
||||
labels,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_labels = labels;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_sub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_labels.isEmpty) {
|
||||
if (!widget.showEmptyText && widget.emptyText == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Text(
|
||||
widget.emptyText ?? context.lang.contactLabelsSubtitleEmpty,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 2,
|
||||
children: _labels.map((label) {
|
||||
final bgColor = Color(label.backgroundColor);
|
||||
final textColor = Color(label.textColor);
|
||||
|
||||
return Container(
|
||||
padding: widget.padding,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
label.name,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget? buildContactLabelsSubtitle({
|
||||
required int contactId,
|
||||
required List<Label> labels,
|
||||
Widget? additionalSubtitle,
|
||||
}) {
|
||||
if (additionalSubtitle != null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
additionalSubtitle,
|
||||
if (labels.isNotEmpty)
|
||||
ContactLabels(contactId: contactId),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (labels.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ContactLabels(contactId: contactId);
|
||||
}
|
||||
|
||||
class ContactLabelsSubtitleBuilder extends StatelessWidget {
|
||||
const ContactLabelsSubtitleBuilder({
|
||||
required this.contactId,
|
||||
required this.builder,
|
||||
this.additionalSubtitle,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int contactId;
|
||||
final Widget? additionalSubtitle;
|
||||
final Widget Function(BuildContext context, Widget? subtitleWidget) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<List<Label>>(
|
||||
stream: twonlyDB.labelsDao.watchContactLabels(contactId),
|
||||
builder: (context, snapshot) {
|
||||
final labels = snapshot.data ?? [];
|
||||
final subtitle = buildContactLabelsSubtitle(
|
||||
contactId: contactId,
|
||||
labels: labels,
|
||||
additionalSubtitle: additionalSubtitle,
|
||||
);
|
||||
return builder(context, subtitle);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class ContactRequestBadgeComp extends StatelessWidget {
|
||||
const ContactRequestBadgeComp({super.key});
|
||||
|
|
@ -26,8 +25,8 @@ class ContactRequestBadgeComp extends StatelessWidget {
|
|||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
114
lib/src/visual/components/custom_color_picker_dialog.comp.dart
Normal file
114
lib/src/visual/components/custom_color_picker_dialog.comp.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
class CustomColorPickerDialog extends StatefulWidget {
|
||||
const CustomColorPickerDialog({
|
||||
required this.initialColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Color initialColor;
|
||||
|
||||
@override
|
||||
State<CustomColorPickerDialog> createState() => _CustomColorPickerDialogState();
|
||||
}
|
||||
|
||||
class _CustomColorPickerDialogState extends State<CustomColorPickerDialog> {
|
||||
late HSVColor _hsvColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hsvColor = HSVColor.fromColor(widget.initialColor);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentColor = _hsvColor.toColor();
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(context.lang.customColor),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Color preview box
|
||||
Center(
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: currentColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Hue Slider
|
||||
Text(context.lang.hue, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
Slider(
|
||||
value: _hsvColor.hue,
|
||||
max: 360,
|
||||
activeColor: HSVColor.fromAHSV(1, _hsvColor.hue, 1, 1).toColor(),
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_hsvColor = _hsvColor.withHue(val);
|
||||
});
|
||||
},
|
||||
),
|
||||
// Saturation Slider
|
||||
Text(context.lang.saturation, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
Slider(
|
||||
value: _hsvColor.saturation,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_hsvColor = _hsvColor.withSaturation(val);
|
||||
});
|
||||
},
|
||||
),
|
||||
// Brightness / Value Slider
|
||||
Text(context.lang.brightness, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
Slider(
|
||||
value: _hsvColor.value,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_hsvColor = _hsvColor.withValue(val);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(context.lang.cancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () => Navigator.of(context).pop(currentColor.toARGB32()),
|
||||
child: Text(context.lang.ok),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
328
lib/src/visual/components/label_editor_bottom_sheet.comp.dart
Normal file
328
lib/src/visual/components/label_editor_bottom_sheet.comp.dart
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/custom_color_picker_dialog.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
class LabelEditorBottomSheet extends StatefulWidget {
|
||||
const LabelEditorBottomSheet({
|
||||
this.label,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Label? label;
|
||||
|
||||
@override
|
||||
State<LabelEditorBottomSheet> createState() => _LabelEditorBottomSheetState();
|
||||
}
|
||||
|
||||
class _LabelEditorBottomSheetState extends State<LabelEditorBottomSheet> {
|
||||
late TextEditingController _nameController;
|
||||
late int _selectedBgColor;
|
||||
late int _selectedTextColor;
|
||||
|
||||
static const List<int> defaultBgColors = [
|
||||
0xFFE57373, // Red
|
||||
0xFFF06292, // Pink
|
||||
0xFFBA68C8, // Purple
|
||||
0xFF7986CB, // Indigo
|
||||
0xFF64B5F6, // Blue
|
||||
0xFF4DD0E1, // Cyan
|
||||
0xFF4DB6AC, // Teal
|
||||
0xFF81C784, // Green
|
||||
0xFFFFB74D, // Amber
|
||||
0xFFFF8A65, // Deep Orange
|
||||
0xFF90A4AE, // Blue Grey
|
||||
0xFF424242, // Dark Grey
|
||||
];
|
||||
|
||||
static const List<int> defaultTextColors = [
|
||||
0xFFFFFFFF, // White
|
||||
0xFF121212, // Dark/Black
|
||||
0xFF1B263B, // Deep Navy
|
||||
0xFF8B0000, // Dark Red
|
||||
0xFF004D40, // Dark Teal
|
||||
0xFF4A148C, // Dark Purple
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.label?.name ?? '');
|
||||
_selectedBgColor = widget.label?.backgroundColor ?? defaultBgColors[4];
|
||||
_selectedTextColor = widget.label?.textColor ?? defaultTextColors[0];
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _pickCustomColor({required bool isBgColor}) async {
|
||||
final initial = isBgColor ? Color(_selectedBgColor) : Color(_selectedTextColor);
|
||||
final pickedColorInt = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => CustomColorPickerDialog(initialColor: initial),
|
||||
);
|
||||
|
||||
if (pickedColorInt != null && mounted) {
|
||||
setState(() {
|
||||
if (isBgColor) {
|
||||
_selectedBgColor = pickedColorInt;
|
||||
} else {
|
||||
_selectedTextColor = pickedColorInt;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEditing = widget.label != null;
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: bottomInset),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Drag indicator handle
|
||||
Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade400,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
isEditing ? context.lang.editLabel : context.lang.createLabel,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Interactive Inline Label Badge
|
||||
Center(
|
||||
child: IntrinsicWidth(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100, maxWidth: 200),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color(_selectedBgColor),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
|
||||
child: TextField(
|
||||
controller: _nameController,
|
||||
autofocus: true,
|
||||
maxLength: 8,
|
||||
textAlign: TextAlign.center,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
style: TextStyle(
|
||||
color: Color(_selectedTextColor),
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: context.lang.labelNameHint,
|
||||
hintStyle: TextStyle(
|
||||
color: Color(_selectedTextColor).withValues(alpha: 0.6),
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
|
||||
counterText: '',
|
||||
),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
// Background Color Section
|
||||
Text(
|
||||
context.lang.labelBackgroundColor,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
...defaultBgColors.map((colorValue) {
|
||||
final selected = _selectedBgColor == colorValue;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedBgColor = colorValue;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(colorValue),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: selected
|
||||
? Icon(
|
||||
Icons.check,
|
||||
size: 18,
|
||||
color: Color(colorValue).computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}),
|
||||
// Custom Color Picker Button
|
||||
GestureDetector(
|
||||
onTap: () => _pickCustomColor(isBgColor: true),
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: SweepGradient(
|
||||
colors: [
|
||||
Colors.red,
|
||||
Colors.yellow,
|
||||
Colors.green,
|
||||
Colors.cyan,
|
||||
Colors.blue,
|
||||
Colors.purple,
|
||||
Colors.red,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.colorize,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Text Color Section
|
||||
Text(
|
||||
context.lang.labelTextColor,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
...defaultTextColors.map((colorValue) {
|
||||
final selected = _selectedTextColor == colorValue;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedTextColor = colorValue;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(colorValue),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: selected
|
||||
? Icon(
|
||||
Icons.check,
|
||||
size: 18,
|
||||
color: Color(colorValue).computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}),
|
||||
// Custom Color Picker Button for Text
|
||||
GestureDetector(
|
||||
onTap: () => _pickCustomColor(isBgColor: false),
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: SweepGradient(
|
||||
colors: [
|
||||
Colors.red,
|
||||
Colors.yellow,
|
||||
Colors.green,
|
||||
Colors.cyan,
|
||||
Colors.blue,
|
||||
Colors.purple,
|
||||
Colors.red,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.colorize,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(context.lang.cancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () {
|
||||
final name = _nameController.text.trim();
|
||||
if (name.isNotEmpty) {
|
||||
Navigator.of(context).pop({
|
||||
'name': name,
|
||||
'backgroundColor': _selectedBgColor,
|
||||
'textColor': _selectedTextColor,
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Text(context.lang.ok),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class SelectableThumbnailComp extends StatelessWidget {
|
||||
const SelectableThumbnailComp({
|
||||
|
|
@ -19,7 +19,7 @@ class SelectableThumbnailComp extends StatelessWidget {
|
|||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.transparent,
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
|
|
@ -49,7 +49,7 @@ class SelectableThumbnailComp extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.black38,
|
||||
color: isSelected ? context.color.primary : Colors.black38,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238);
|
||||
|
||||
|
|
@ -90,7 +89,7 @@ class VerificationBadgeInfo extends StatelessWidget {
|
|||
context,
|
||||
icon: const SvgIcon(assetPath: SvgIcons.verifiedGreen, size: 40),
|
||||
description: context.lang.verificationBadgeGreenDesc,
|
||||
boldTextColor: primaryColor,
|
||||
boldTextColor: context.color.primary,
|
||||
onTap: () => context.push(
|
||||
Routes.cameraQRScanner,
|
||||
extra: {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class VerificationSuccessAnimationState
|
|||
'<svg viewBox="0 0 640 640"><path d="$_path2" fill="white"/></svg>';
|
||||
|
||||
static const _grey = Color(0xFF8E9AAF);
|
||||
static const Color _green = primaryColor;
|
||||
static const Color _green = defaultPrimaryColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class BetterText extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
// Regular expression to find URLs and domains
|
||||
final urlRegExp = RegExp(
|
||||
r'(?:(?:https?://|www\.)[^\s]+|(?:[a-zA-Z0-9-]+\.[a-zA-Z]{2,}))',
|
||||
r'''(?:(?:https?://|www\.)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))|(?:(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,})''',
|
||||
caseSensitive: false,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/reactive_tap_feedback.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
enum MyButtonVariant {
|
||||
primary,
|
||||
|
|
@ -49,7 +48,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
switch (widget.variant) {
|
||||
case MyButtonVariant.primary:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
|
|
@ -96,7 +95,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.primaryMiddle:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
|
|
@ -115,7 +114,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.primaryDense:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/reactive_tap_feedback.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
enum MyIconButtonVariant {
|
||||
primary,
|
||||
|
|
@ -43,7 +42,7 @@ class _MyIconButtonState extends State<MyIconButton> {
|
|||
late final Color fgColor;
|
||||
|
||||
if (widget.variant == MyIconButtonVariant.primary) {
|
||||
bgColor = primaryColor;
|
||||
bgColor = context.color.primary;
|
||||
fgColor = Colors.black87;
|
||||
} else {
|
||||
bgColor = isDark ? Colors.grey[800]! : Colors.grey[200]!;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
final ThemeData darkTheme = () {
|
||||
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) {
|
||||
final base = ThemeData.dark().copyWith(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
brightness: Brightness.dark,
|
||||
seedColor: const Color(0xFF57CC99),
|
||||
seedColor: primary,
|
||||
primary: primary,
|
||||
surface: const Color.fromARGB(255, 20, 18, 23),
|
||||
surfaceContainer: const Color.fromARGB(255, 45, 41, 54),
|
||||
surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45),
|
||||
|
|
@ -21,4 +23,6 @@ final ThemeData darkTheme = () {
|
|||
fontFamilyFallback: Platform.isAndroid ? const ['NotoColorEmoji'] : null,
|
||||
),
|
||||
);
|
||||
}();
|
||||
}
|
||||
|
||||
final ThemeData darkTheme = getDarkTheme();
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
const primaryColor = Color(0xFF57CC99);
|
||||
const defaultPrimaryColor = Color(0xFF57CC99);
|
||||
|
||||
final ThemeData lightTheme = () {
|
||||
ThemeData getLightTheme([Color primary = defaultPrimaryColor]) {
|
||||
final base = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: primaryColor,
|
||||
seedColor: primary,
|
||||
primary: primary,
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
|
|
@ -19,10 +20,12 @@ final ThemeData lightTheme = () {
|
|||
fontFamilyFallback: Platform.isAndroid ? const ['NotoColorEmoji'] : null,
|
||||
),
|
||||
);
|
||||
}();
|
||||
}
|
||||
|
||||
final ThemeData lightTheme = getLightTheme();
|
||||
|
||||
final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: defaultPrimaryColor,
|
||||
foregroundColor: Colors.black87,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class CameraScannedOverlay extends StatelessWidget {
|
|||
width: 150,
|
||||
child: ListView(
|
||||
children: [
|
||||
if (mainController.scannedUrl != null)
|
||||
if (mainController.scannedUrl != null &&
|
||||
mainController.scannedUrl!.isNotEmpty)
|
||||
_buildScannedUrlTile(context, mainController.scannedUrl!),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
super.initState();
|
||||
initVolumeControl();
|
||||
initAsync();
|
||||
_checkAndInitCamera();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -176,12 +177,26 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
if (oldWidget.isVisible != widget.isVisible) {
|
||||
if (widget.isVisible) {
|
||||
initVolumeControl();
|
||||
_checkAndInitCamera();
|
||||
} else {
|
||||
_deInitVolumeControl();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkAndInitCamera() {
|
||||
if (widget.isVisible &&
|
||||
mc.cameraController == null &&
|
||||
!mc.initCameraStarted) {
|
||||
unawaited(
|
||||
mc.selectCamera(
|
||||
mc.selectedCameraDetails.cameraId,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_videoRecordingTimer?.cancel();
|
||||
|
|
@ -686,6 +701,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
Widget build(BuildContext context) {
|
||||
if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length ||
|
||||
mc.cameraController == null) {
|
||||
if (widget.isVisible &&
|
||||
!mc.initCameraStarted &&
|
||||
!mc.isSharePreviewIsShown) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkAndInitCamera();
|
||||
});
|
||||
}
|
||||
return Container();
|
||||
}
|
||||
return StreamBuilder(
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class MainCameraController {
|
|||
}
|
||||
|
||||
void onImageSend() {
|
||||
scannedUrl = '';
|
||||
scannedUrl = null;
|
||||
setState?.call();
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +155,7 @@ class MainCameraController {
|
|||
} catch (e) {
|
||||
Log.error('Error querying available cameras: $e');
|
||||
initCameraStarted = false;
|
||||
setState?.call();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +165,7 @@ class MainCameraController {
|
|||
'Trying to select a non existing camera $cameraId >= ${AppEnvironment.cameras.length}',
|
||||
);
|
||||
initCameraStarted = false;
|
||||
setState?.call();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +194,7 @@ class MainCameraController {
|
|||
final hasMic = await micPermissionFuture;
|
||||
if (sessionId != _cameraSessionId) return;
|
||||
|
||||
final controller = CameraController(
|
||||
var controller = CameraController(
|
||||
AppEnvironment.cameras[cameraId],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: hasMic,
|
||||
|
|
@ -204,7 +206,23 @@ class MainCameraController {
|
|||
var assignedToGlobal = false;
|
||||
try {
|
||||
_initializeFuture = controller.initialize();
|
||||
try {
|
||||
await _initializeFuture;
|
||||
} on CameraException catch (e) {
|
||||
// If specific image format is unsupported on this hardware, fallback to default format
|
||||
Log.warn(
|
||||
'Initial camera format initialization failed ($e), trying fallback format...',
|
||||
);
|
||||
await controller.dispose();
|
||||
controller = CameraController(
|
||||
AppEnvironment.cameras[cameraId],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: hasMic,
|
||||
);
|
||||
_initializeFuture = controller.initialize();
|
||||
await _initializeFuture;
|
||||
}
|
||||
|
||||
if (sessionId != _cameraSessionId) {
|
||||
unawaited(controller.dispose());
|
||||
return;
|
||||
|
|
@ -256,6 +274,7 @@ class MainCameraController {
|
|||
unawaited(controller.dispose());
|
||||
}
|
||||
initCameraStarted = false;
|
||||
setState?.call();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/key_verification.service.dart';
|
||||
|
|
@ -8,6 +10,7 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
||||
|
||||
class QrCodeScannerView extends StatefulWidget {
|
||||
const QrCodeScannerView({
|
||||
|
|
@ -41,6 +44,7 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
|||
}
|
||||
Permission.camera.isGranted.then((hasPermission) {
|
||||
if (hasPermission && mounted) {
|
||||
AppState.hasCameraPermissions = true;
|
||||
unawaited(_mainCameraController.selectCamera(0, true));
|
||||
}
|
||||
});
|
||||
|
|
@ -100,6 +104,17 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
// Always-visible back button so the user can exit regardless of
|
||||
// camera state (e.g. during init or after init failure).
|
||||
Positioned(
|
||||
left: 5,
|
||||
top: MediaQuery.paddingOf(context).top + 10,
|
||||
child: ActionButton(
|
||||
FontAwesomeIcons.xmark,
|
||||
tooltipText: context.lang.close,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
||||
|
||||
class CameraSendToView extends StatefulWidget {
|
||||
const CameraSendToView(this.sendToGroup, {super.key});
|
||||
|
|
@ -24,6 +28,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
};
|
||||
Permission.camera.isGranted.then((hasPermission) {
|
||||
if (hasPermission && mounted) {
|
||||
AppState.hasCameraPermissions = true;
|
||||
unawaited(_mainCameraController.selectCamera(0, true));
|
||||
}
|
||||
});
|
||||
|
|
@ -58,6 +63,17 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
isVisible: true,
|
||||
),
|
||||
),
|
||||
// Always-visible back button so the user can exit regardless of
|
||||
// camera state (e.g. during init or after init failure).
|
||||
Positioned(
|
||||
left: 5,
|
||||
top: MediaQuery.paddingOf(context).top + 10,
|
||||
child: ActionButton(
|
||||
FontAwesomeIcons.xmark,
|
||||
tooltipText: context.lang.close,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -310,10 +310,12 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
});
|
||||
|
||||
// in case mediaStoreFutureReady is ready, the image is stored in the originalPath
|
||||
await insertMediaFileInMessagesTable(
|
||||
unawaited(
|
||||
insertMediaFileInMessagesTable(
|
||||
widget.mediaFileService,
|
||||
widget.selectedGroupIds.toList(),
|
||||
additionalData: widget.additionalData,
|
||||
),
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
|
|
|
|||
|
|
@ -359,11 +359,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
context.lang.dialogAskDeleteMediaFilePopTitle,
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
child: Text(context.lang.dialogAskDeleteMediaFilePopDelete),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: Text(context.lang.dialogAskDeleteMediaFilePopDelete),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(context.lang.cancel),
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class _ArchivedChatsViewState extends State<ArchivedChatsView> {
|
|||
body: ListView(
|
||||
children: _groupsArchived.map((group) {
|
||||
return GroupListItemComp(
|
||||
key: ValueKey(group.groupId),
|
||||
group: group,
|
||||
);
|
||||
}).toList(),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/connection_status.comp.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/empty_chat_list.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/group_list_item.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/news_btn.comp.dart';
|
||||
|
|
@ -196,8 +195,8 @@ class _ChatListViewState extends State<ChatListView> with AutomaticKeepAliveClie
|
|||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
@ -329,7 +328,7 @@ class _ChatListViewState extends State<ChatListView> with AutomaticKeepAliveClie
|
|||
FloatingActionButton(
|
||||
heroTag: 'new_chat_fab',
|
||||
elevation: 2,
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
onPressed: () => context.push(Routes.chatsStartNewChat),
|
||||
child: const FaIcon(
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import 'package:go_router/go_router.dart';
|
|||
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/context_menu/group.context_menu.dart';
|
||||
|
|
@ -42,6 +42,8 @@ class _UserListItem extends State<GroupListItemComp> {
|
|||
StreamSubscription<Message?>? _lastMessageStream;
|
||||
StreamSubscription<Reaction?>? _lastReactionStream;
|
||||
StreamSubscription<List<MediaFile>>? _lastMediaFilesStream;
|
||||
Contact? _directContact;
|
||||
StreamSubscription<List<Contact>>? _directContactStream;
|
||||
|
||||
List<Message> _previewMessages = [];
|
||||
final List<MediaFile> _previewMediaFiles = [];
|
||||
|
|
@ -60,6 +62,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
|||
_lastReactionStream?.cancel();
|
||||
_lastMessageStream?.cancel();
|
||||
_lastMediaFilesStream?.cancel();
|
||||
_directContactStream?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -102,6 +105,19 @@ class _UserListItem extends State<GroupListItemComp> {
|
|||
setState(() {});
|
||||
});
|
||||
|
||||
if (widget.group.isDirectChat) {
|
||||
_directContactStream = twonlyDB.groupsDao
|
||||
.watchGroupContact(widget.group.groupId)
|
||||
.listen((contacts) {
|
||||
if (!mounted) return;
|
||||
if (contacts.isNotEmpty) {
|
||||
setState(() {
|
||||
_directContact = contacts.first;
|
||||
_receiverDeletedAccount = _directContact!.accountDeleted;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final groupContacts = await twonlyDB.groupsDao.getGroupContact(
|
||||
widget.group.groupId,
|
||||
);
|
||||
|
|
@ -110,6 +126,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
|||
_receiverDeletedAccount = groupContacts.first.accountDeleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateState(
|
||||
Message? newLastMessage,
|
||||
|
|
@ -239,8 +256,12 @@ class _UserListItem extends State<GroupListItemComp> {
|
|||
child: ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(
|
||||
substringBy(widget.group.groupName, 30),
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.group.groupName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
VerificationBadgeComp(
|
||||
|
|
@ -249,6 +270,18 @@ class _UserListItem extends State<GroupListItemComp> {
|
|||
clickable: false,
|
||||
size: 12,
|
||||
),
|
||||
if (widget.group.isDirectChat && _directContact != null) ...[
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: ContactLabels(
|
||||
contactId: _directContact!.userId,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
subtitle: _receiverDeletedAccount
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
|
|
@ -42,12 +41,12 @@ class _LastMessageTimeCompState extends State<LastMessageTimeComp> {
|
|||
_actionSubscription = null;
|
||||
|
||||
if (widget.message != null) {
|
||||
_actionSubscription = twonlyDB.messagesDao
|
||||
.watchLastMessageAction(widget.message!.messageId)
|
||||
.listen((lastAction) {
|
||||
targetTime = lastAction?.actionAt ?? widget.message!.createdAt;
|
||||
if (widget.message!.senderId == null) {
|
||||
targetTime = widget.message!.openedAt ?? widget.message!.createdAt;
|
||||
} else {
|
||||
targetTime = widget.message!.createdAt;
|
||||
}
|
||||
_updateSeconds();
|
||||
});
|
||||
} else if (widget.dateTime != null) {
|
||||
targetTime = widget.dateTime;
|
||||
_updateSeconds();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class NewsIconButtonComp extends StatelessWidget {
|
||||
const NewsIconButtonComp({super.key});
|
||||
|
|
@ -23,7 +22,7 @@ class NewsIconButtonComp extends StatelessWidget {
|
|||
builder: (context, count, child) {
|
||||
return NotificationBadgeComp(
|
||||
count: count.toString(),
|
||||
backgroundColor: primaryColor,
|
||||
backgroundColor: context.color.primary,
|
||||
textColor: Colors.black87,
|
||||
child: IconButton(
|
||||
onPressed: () => context.push(Routes.settingsHelpNews),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:twonly/src/services/api/messages.api.dart';
|
|||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
|
|
@ -234,13 +235,20 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
|||
messages = chatItems.reversed.toList();
|
||||
});
|
||||
|
||||
if (wasSentByMe && itemScrollController.isAttached) {
|
||||
if (wasSentByMe) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || !itemScrollController.isAttached) return;
|
||||
try {
|
||||
unawaited(
|
||||
itemScrollController.scrollTo(
|
||||
index: 0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
// Ignore if the inner scroll controller is still not attached
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final items = await MemoryItem.convertFromMessages(storedMediaFiles);
|
||||
|
|
@ -303,7 +311,11 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
|||
Expanded(
|
||||
child: ColoredBox(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
substringBy(group.groupName, 20),
|
||||
|
|
@ -317,6 +329,23 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
|||
FlameCounterWidget(groupId: group.groupId),
|
||||
],
|
||||
),
|
||||
if (group.isDirectChat)
|
||||
StreamBuilder<List<Contact>>(
|
||||
stream: twonlyDB.groupsDao.watchGroupContact(
|
||||
group.groupId,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
final contacts = snapshot.data ?? [];
|
||||
if (contacts.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return ContactLabels(
|
||||
contactId: contacts.first.userId,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dar
|
|||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/emoji_picker.bottom.dart';
|
||||
import 'package:twonly/src/visual/context_menu/context_menu.helper.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
|
||||
import 'package:twonly/src/visual/views/chats/message_info.view.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_list_entry.dart';
|
||||
import 'package:twonly/src/visual/views/memories/synchronized_viewer.view.dart';
|
||||
|
||||
class MessageContextMenu extends StatelessWidget {
|
||||
|
|
@ -83,8 +84,9 @@ class MessageContextMenu extends StatelessWidget {
|
|||
return SynchronizedImageViewerScreen(
|
||||
galleryItems: galleryItems,
|
||||
initialIndex: 0,
|
||||
activeMediaIdNotifier:
|
||||
ValueNotifier(mediaFileService!.mediaFile.mediaId),
|
||||
activeMediaIdNotifier: ValueNotifier(
|
||||
mediaFileService!.mediaFile.mediaId,
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
|
|
@ -172,17 +174,13 @@ class MessageContextMenu extends StatelessWidget {
|
|||
ContextMenuItem(
|
||||
title: context.lang.delete,
|
||||
onTap: () async {
|
||||
final delete = await showAlertDialog(
|
||||
final action = await showDeleteMessageOptions(
|
||||
navigator.context,
|
||||
navigator.context.lang.deleteTitle,
|
||||
null,
|
||||
customOk:
|
||||
(message.senderId == null && !message.isDeletedFromSender)
|
||||
? navigator.context.lang.deleteOkBtnForAll
|
||||
: navigator.context.lang.deleteOkBtnForMe,
|
||||
message,
|
||||
group,
|
||||
galleryItems,
|
||||
);
|
||||
if (delete) {
|
||||
if (message.senderId == null && !message.isDeletedFromSender) {
|
||||
if (action == 'delete_for_all') {
|
||||
await twonlyDB.messagesDao.handleMessageDeletion(
|
||||
null,
|
||||
message.messageId,
|
||||
|
|
@ -197,12 +195,11 @@ class MessageContextMenu extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
} else if (action == 'delete_for_me') {
|
||||
await twonlyDB.messagesDao.deleteMessagesById(
|
||||
message.messageId,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: FontAwesomeIcons.trash,
|
||||
),
|
||||
|
|
@ -230,22 +227,138 @@ class MessageContextMenu extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
Future<String?> showDeleteMessageOptions(
|
||||
BuildContext context,
|
||||
Message message,
|
||||
Group group,
|
||||
List<MemoryItem> galleryItems,
|
||||
) async {
|
||||
return showModalBottomSheet<String>(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
final isForAll = message.senderId == null && !message.isDeletedFromSender;
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
bottom: 40,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.surfaceContainer,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
ChatListEntry(
|
||||
group: group,
|
||||
message: message,
|
||||
galleryItems: galleryItems,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// Prevent clicks
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Divider(),
|
||||
const SizedBox(height: 24),
|
||||
if (isForAll) ...[
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.errorMiddle,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'delete_for_all');
|
||||
},
|
||||
child: Text(
|
||||
context.lang.deleteOkBtnForAll,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: isForAll
|
||||
? MyButtonVariant.secondaryDense
|
||||
: MyButtonVariant.errorMiddle,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'delete_for_me');
|
||||
},
|
||||
child: Text(
|
||||
isForAll
|
||||
? context.lang.deleteOnlyForMe
|
||||
: context.lang.deleteOkBtnForMe,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'cancel');
|
||||
},
|
||||
child: Text(context.lang.cancel, textAlign: TextAlign.center),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> editTextMessage(BuildContext context, Message message) async {
|
||||
var newText = message.content;
|
||||
final controller = TextEditingController(text: message.content);
|
||||
await showDialog(
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: StatefulBuilder(
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return SingleChildScrollView(
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
bottom: 40,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.surfaceContainer,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: TextField(
|
||||
TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
|
|
@ -259,20 +372,25 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
|
|||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryMiddle,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.lang.cancel),
|
||||
child: Text(
|
||||
context.lang.cancel,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
TextButton(
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () async {
|
||||
if (newText != null &&
|
||||
newText != message.content &&
|
||||
|
|
@ -288,8 +406,11 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
|
|||
await sendCipherTextToGroup(
|
||||
message.groupId,
|
||||
pb.EncryptedContent(
|
||||
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
||||
type: pb.EncryptedContent_MessageUpdate_Type.EDIT_TEXT,
|
||||
messageUpdate:
|
||||
pb.EncryptedContent_MessageUpdate(
|
||||
type: pb
|
||||
.EncryptedContent_MessageUpdate_Type
|
||||
.EDIT_TEXT,
|
||||
senderMessageId: message.messageId,
|
||||
text: newText,
|
||||
timestamp: Int64(
|
||||
|
|
@ -302,9 +423,19 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
|
|||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.lang.ok),
|
||||
child: Text(
|
||||
context.lang.ok,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ class _MessageInputState extends State<MessageInput> {
|
|||
Timer? _nextTypingIndicator;
|
||||
DateTime? _lastTextChangeTime;
|
||||
int? _contactId;
|
||||
Timer? _recordingTimer;
|
||||
DateTime? _recordingStartTime;
|
||||
|
||||
Future<void> _sendMessage() async {
|
||||
if (_textFieldController.text == '') return;
|
||||
|
|
@ -104,6 +106,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||
_textFieldController.removeListener(_handleTextChange);
|
||||
widget.textFieldFocus.removeListener(_handleTextFocusChange);
|
||||
widget.textFieldFocus.dispose();
|
||||
_recordingTimer?.cancel();
|
||||
recorderController.dispose();
|
||||
_nextTypingIndicator?.cancel();
|
||||
|
||||
|
|
@ -124,12 +127,6 @@ class _MessageInputState extends State<MessageInput> {
|
|||
|
||||
void _initializeControllers() {
|
||||
recorderController = RecorderController();
|
||||
recorderController.onCurrentDuration.listen((duration) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_currentDuration = duration.inMilliseconds;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _handleTextChange() {
|
||||
|
|
@ -161,6 +158,21 @@ class _MessageInputState extends State<MessageInput> {
|
|||
_recordingState = RecordingState.recording;
|
||||
_currentDuration = 0;
|
||||
});
|
||||
_recordingStartTime = clock.now();
|
||||
_recordingTimer?.cancel();
|
||||
_recordingTimer = Timer.periodic(const Duration(milliseconds: 100), (
|
||||
timer,
|
||||
) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
if (_recordingStartTime != null) {
|
||||
_currentDuration = clock
|
||||
.now()
|
||||
.difference(_recordingStartTime!)
|
||||
.inMilliseconds;
|
||||
}
|
||||
});
|
||||
});
|
||||
await HapticFeedback.heavyImpact();
|
||||
final audioTmpPath = '${AppEnvironment.cacheDir}/recording.m4a';
|
||||
unawaited(
|
||||
|
|
@ -171,6 +183,8 @@ class _MessageInputState extends State<MessageInput> {
|
|||
}
|
||||
|
||||
Future<void> _stopAudioRecording() async {
|
||||
_recordingTimer?.cancel();
|
||||
_recordingTimer = null;
|
||||
await HapticFeedback.heavyImpact();
|
||||
setState(() {
|
||||
_audioRecordingLock = false;
|
||||
|
|
@ -200,6 +214,8 @@ class _MessageInputState extends State<MessageInput> {
|
|||
}
|
||||
|
||||
Future<void> _cancelAudioRecording() async {
|
||||
_recordingTimer?.cancel();
|
||||
_recordingTimer = null;
|
||||
setState(() {
|
||||
_audioRecordingLock = false;
|
||||
_cancelSlideOffset = 0;
|
||||
|
|
@ -330,8 +346,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||
TextField(
|
||||
controller: _textFieldController,
|
||||
focusNode: widget.textFieldFocus,
|
||||
textCapitalization:
|
||||
TextCapitalization.sentences,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
keyboardType: TextInputType.multiline,
|
||||
showCursor:
|
||||
_recordingState != RecordingState.recording,
|
||||
|
|
@ -385,13 +400,10 @@ class _MessageInputState extends State<MessageInput> {
|
|||
),
|
||||
if (!_audioRecordingLock) ...[
|
||||
SizedBox(
|
||||
width:
|
||||
(100 - _cancelSlideOffset) % 101,
|
||||
width: (100 - _cancelSlideOffset) % 101,
|
||||
),
|
||||
Text(
|
||||
context
|
||||
.lang
|
||||
.voiceMessageSlideToCancel,
|
||||
context.lang.voiceMessageSlideToCancel,
|
||||
),
|
||||
] else ...[
|
||||
Expanded(
|
||||
|
|
@ -476,8 +488,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
if (_recordingState ==
|
||||
RecordingState.recording &&
|
||||
if (_recordingState == RecordingState.recording &&
|
||||
!_audioRecordingLock)
|
||||
Positioned.fill(
|
||||
top: -120,
|
||||
|
|
@ -513,8 +524,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (_recordingState ==
|
||||
RecordingState.recording &&
|
||||
if (_recordingState == RecordingState.recording &&
|
||||
!_audioRecordingLock)
|
||||
Positioned.fill(
|
||||
top: -20,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
|
|
@ -206,7 +207,15 @@ class _MessageInfoViewState extends State<MessageInfoView> {
|
|||
'${context.lang.received}: ${friendlyDateTime(context, widget.message.ackByServer!)}',
|
||||
),
|
||||
if (userService.currentUser.isDeveloper)
|
||||
Text('ID: ${widget.message.messageId}'),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: widget.message.messageId),
|
||||
);
|
||||
await HapticFeedback.heavyImpact();
|
||||
},
|
||||
child: Text('ID: ${widget.message.messageId}'),
|
||||
),
|
||||
if (messageHistory.isNotEmpty) ...[
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/context_menu/group.context_menu.dart';
|
||||
|
|
@ -216,32 +217,39 @@ class _StartNewChatView extends State<StartNewChatView> {
|
|||
}
|
||||
|
||||
if (i < filteredContacts.length) {
|
||||
final contact = filteredContacts[i];
|
||||
return UserContextMenu(
|
||||
key: ValueKey(filteredContacts[i].userId),
|
||||
contact: filteredContacts[i],
|
||||
child: ListTile(
|
||||
key: ValueKey(contact.userId),
|
||||
contact: contact,
|
||||
child: ContactLabelsSubtitleBuilder(
|
||||
contactId: contact.userId,
|
||||
builder: (context, subtitleWidget) {
|
||||
return ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(getContactDisplayName(filteredContacts[i])),
|
||||
Text(getContactDisplayName(contact)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8,
|
||||
left: 1,
|
||||
),
|
||||
child: VerificationBadgeComp(
|
||||
contact: filteredContacts[i],
|
||||
contact: contact,
|
||||
),
|
||||
),
|
||||
FlameCounterWidget(
|
||||
contactId: filteredContacts[i].userId,
|
||||
contactId: contact.userId,
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: subtitleWidget,
|
||||
leading: AvatarIcon(
|
||||
contactId: filteredContacts[i].userId,
|
||||
contactId: contact.userId,
|
||||
fontSize: 13,
|
||||
),
|
||||
onTap: () => _onTapUser(filteredContacts[i]),
|
||||
onTap: () => _onTapUser(contact),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
|
|
@ -20,6 +23,7 @@ import 'package:twonly/src/visual/views/contact/contact_components/mutual_groups
|
|||
import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/contact_components/verification_expansion_tile.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/select_contact_labels.view.dart';
|
||||
import 'package:twonly/src/visual/views/groups/group.view.dart';
|
||||
|
||||
class ContactView extends StatefulWidget {
|
||||
|
|
@ -225,6 +229,21 @@ class _ContactViewState extends State<ContactView> {
|
|||
userService.currentUser.userId,
|
||||
),
|
||||
),
|
||||
ContactLabelsSubtitleBuilder(
|
||||
contactId: contact.userId,
|
||||
builder: (context, subtitleWidget) {
|
||||
return BetterListTile(
|
||||
icon: FontAwesomeIcons.tag,
|
||||
text: context.lang.contactLabelsTitle,
|
||||
subtitle: subtitleWidget,
|
||||
onTap: () {
|
||||
context.navPush(
|
||||
SelectContactLabelsView(contactId: contact.userId),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
RestoreFlameComp(
|
||||
contactId: widget.userId,
|
||||
|
|
@ -256,6 +275,48 @@ class _ContactViewState extends State<ContactView> {
|
|||
text: context.lang.contactRemove,
|
||||
onTap: () => handleUserRemoveRequest(contact),
|
||||
),
|
||||
if (userService.currentUser.isDeveloper) ...[
|
||||
if (contact.signalVersion != SignalVersion.v2)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.arrowsRotate,
|
||||
text: 'Update Connection to V2 (PQXDH)',
|
||||
onTap: () async {
|
||||
final userData = await apiService.getUserById(contact.userId);
|
||||
if (userData != null) {
|
||||
await processSignalUserData(userData);
|
||||
final updatedContact = await twonlyDB.contactsDao
|
||||
.getContactById(contact.userId);
|
||||
final isV2 =
|
||||
updatedContact?.signalVersion == SignalVersion.v2;
|
||||
|
||||
if (context.mounted) {
|
||||
showSnackbar(
|
||||
context,
|
||||
isV2
|
||||
? 'Connection updated to V2 successfully'
|
||||
: 'Failed to update connection to V2',
|
||||
level: isV2
|
||||
? SnackbarLevel.success
|
||||
: SnackbarLevel.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Encryption: ${switch (contact.signalVersion) {
|
||||
SignalVersion.v1 => 'Signal Protocol (v1)',
|
||||
SignalVersion.v2 => 'PQXDH (v2)',
|
||||
}}',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -107,6 +107,9 @@ class _RestoreFlameCompState extends State<RestoreFlameComp> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!userService.currentUser.showRestoreFlame) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
if (_group == null || !isItPossibleToRestoreFlames(_group!)) {
|
||||
return Container();
|
||||
}
|
||||
|
|
@ -115,7 +118,7 @@ class _RestoreFlameCompState extends State<RestoreFlameComp> {
|
|||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: _restoreFlames,
|
||||
title: Text(
|
||||
'Restore your ${_group!.maxFlameCounter} lost flames',
|
||||
context.lang.restoreLostFlames(_group!.maxFlameCounter),
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
trailing: const SizedBox(
|
||||
|
|
@ -134,7 +137,7 @@ class _RestoreFlameCompState extends State<RestoreFlameComp> {
|
|||
emoji: '🔥',
|
||||
),
|
||||
),
|
||||
text: 'Restore your ${_group!.maxFlameCounter} lost flames',
|
||||
text: context.lang.restoreLostFlames(_group!.maxFlameCounter),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
313
lib/src/visual/views/contact/select_contact_labels.view.dart
Normal file
313
lib/src/visual/views/contact/select_contact_labels.view.dart
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/label_editor_bottom_sheet.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
class SelectContactLabelsView extends StatefulWidget {
|
||||
const SelectContactLabelsView({
|
||||
required this.contactId,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int contactId;
|
||||
|
||||
@override
|
||||
State<SelectContactLabelsView> createState() => _SelectContactLabelsViewState();
|
||||
}
|
||||
|
||||
class _SelectContactLabelsViewState extends State<SelectContactLabelsView> {
|
||||
Contact? _contact;
|
||||
List<Label> _allLabels = [];
|
||||
Set<int> _selectedLabelIds = {};
|
||||
|
||||
late StreamSubscription<Contact?> _contactSub;
|
||||
late StreamSubscription<List<Label>> _allLabelsSub;
|
||||
late StreamSubscription<List<Label>> _contactLabelsSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_contactSub = twonlyDB.contactsDao.watchContact(widget.contactId).listen((contact) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_contact = contact;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_allLabelsSub = twonlyDB.labelsDao.watchAllLabels().listen((labels) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_allLabels = labels;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_contactLabelsSub = twonlyDB.labelsDao.watchContactLabels(widget.contactId).listen((labels) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedLabelIds = labels.map((l) => l.id).toSet();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_contactSub.cancel();
|
||||
_allLabelsSub.cancel();
|
||||
_contactLabelsSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _toggleLabel(int labelId) async {
|
||||
final newSet = Set<int>.from(_selectedLabelIds);
|
||||
if (newSet.contains(labelId)) {
|
||||
newSet.remove(labelId);
|
||||
} else {
|
||||
if (newSet.length >= 3) {
|
||||
showSnackbar(
|
||||
context,
|
||||
context.lang.contactLabelsMaxLimit,
|
||||
level: SnackbarLevel.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
newSet.add(labelId);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_selectedLabelIds = newSet;
|
||||
});
|
||||
|
||||
await twonlyDB.labelsDao.setContactLabels(widget.contactId, _selectedLabelIds.toList());
|
||||
}
|
||||
|
||||
Future<void> _createLabel() async {
|
||||
final result = await showModalBottomSheet<Map<String, dynamic>>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => const LabelEditorBottomSheet(),
|
||||
);
|
||||
|
||||
if (result != null && mounted) {
|
||||
final name = result['name'] as String;
|
||||
final bgColor = result['backgroundColor'] as int;
|
||||
final textColor = result['textColor'] as int;
|
||||
|
||||
await twonlyDB.labelsDao.createLabel(name, textColor, bgColor);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _editLabel(Label label) async {
|
||||
final result = await showModalBottomSheet<Map<String, dynamic>>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => LabelEditorBottomSheet(label: label),
|
||||
);
|
||||
|
||||
if (result != null && mounted) {
|
||||
final name = result['name'] as String;
|
||||
final bgColor = result['backgroundColor'] as int;
|
||||
final textColor = result['textColor'] as int;
|
||||
|
||||
await twonlyDB.labelsDao.updateLabel(label.id, name, textColor, bgColor);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteLabel(Label label) async {
|
||||
final confirm = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.lang.deleteLabel),
|
||||
content: Text(context.lang.deleteLabelConfirmation),
|
||||
actions: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(context.lang.cancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.errorMiddle,
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(context.lang.deleteLabel),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if ((confirm ?? false) && mounted) {
|
||||
await twonlyDB.labelsDao.deleteLabel(label.id);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildContactHeader() {
|
||||
if (_contact == null) return const SizedBox.shrink();
|
||||
final contact = _contact!;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
AvatarIcon(contactId: contact.userId, fontSize: 24),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
getContactDisplayName(contact, maxLength: 25),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (getContactDisplayName(contact) != contact.username)
|
||||
Text(
|
||||
'@${contact.username}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ContactLabels(contactId: contact.userId),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.contactLabelsTitle),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
tooltip: context.lang.createLabel,
|
||||
onPressed: _createLabel,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
_buildContactHeader(),
|
||||
Expanded(
|
||||
child: _allLabels.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.contactLabelsSubtitleEmpty,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: _createLabel,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add, size: 20),
|
||||
const SizedBox(width: 6),
|
||||
Text(context.lang.createLabel),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: _allLabels.length,
|
||||
itemBuilder: (context, index) {
|
||||
final label = _allLabels[index];
|
||||
final isSelected = _selectedLabelIds.contains(label.id);
|
||||
|
||||
return CheckboxListTile(
|
||||
value: isSelected,
|
||||
onChanged: (_) => _toggleLabel(label.id),
|
||||
title: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(label.backgroundColor),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
label.name,
|
||||
style: TextStyle(
|
||||
color: Color(label.textColor),
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
secondary: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Tooltip(
|
||||
message: context.lang.editLabel,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => _editLabel(label),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: FaIcon(FontAwesomeIcons.penToSquare, size: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Tooltip(
|
||||
message: context.lang.deleteLabel,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => _deleteLabel(label),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: FaIcon(FontAwesomeIcons.trashCan, size: 16, color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import 'package:twonly/src/services/group.service.dart';
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/select_chat_deletion_time.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
|
|
@ -248,7 +249,10 @@ class _GroupViewState extends State<GroupView> {
|
|||
group: _group!,
|
||||
contact: member.$1,
|
||||
member: member.$2,
|
||||
child: BetterListTile(
|
||||
child: ContactLabelsSubtitleBuilder(
|
||||
contactId: member.$1.userId,
|
||||
builder: (context, subtitleWidget) {
|
||||
return BetterListTile(
|
||||
padding: const EdgeInsets.only(left: 13),
|
||||
leading: AvatarIcon(
|
||||
contactId: member.$1.userId,
|
||||
|
|
@ -266,6 +270,7 @@ class _GroupViewState extends State<GroupView> {
|
|||
),
|
||||
],
|
||||
),
|
||||
subtitle: subtitleWidget,
|
||||
trailing: (member.$2.memberState == MemberState.admin)
|
||||
? Text(context.lang.admin)
|
||||
: null,
|
||||
|
|
@ -277,6 +282,8 @@ class _GroupViewState extends State<GroupView> {
|
|||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ import 'package:twonly/src/database/twonly.db.dart';
|
|||
import 'package:twonly/src/services/group.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/context_menu/user.context_menu.dart';
|
||||
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
class GroupCreateSelectGroupNameView extends StatefulWidget {
|
||||
const GroupCreateSelectGroupNameView({
|
||||
|
|
@ -59,20 +62,28 @@ class _GroupCreateSelectGroupNameViewState
|
|||
title: Text(context.lang.selectGroupName),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
floatingActionButton: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: (textFieldGroupName.text.isEmpty || _isLoading)
|
||||
? null
|
||||
: _createNewGroup,
|
||||
label: Text(context.lang.createGroup),
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_isLoading)
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
height: 15,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 1,
|
||||
),
|
||||
)
|
||||
: const FaIcon(FontAwesomeIcons.penToSquare),
|
||||
else
|
||||
const FaIcon(FontAwesomeIcons.penToSquare, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(context.lang.createGroup),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
|
|
@ -111,20 +122,35 @@ class _GroupCreateSelectGroupNameViewState
|
|||
return UserContextMenu(
|
||||
key: ValueKey(user.userId),
|
||||
contact: user,
|
||||
child: ListTile(
|
||||
child: ContactLabelsSubtitleBuilder(
|
||||
contactId: user.userId,
|
||||
builder: (context, subtitleWidget) {
|
||||
return ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(getContactDisplayName(user)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8,
|
||||
left: 1,
|
||||
),
|
||||
child: VerificationBadgeComp(
|
||||
contact: user,
|
||||
),
|
||||
),
|
||||
FlameCounterWidget(
|
||||
contactId: user.userId,
|
||||
prefix: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: subtitleWidget,
|
||||
leading: AvatarIcon(
|
||||
contactId: user.userId,
|
||||
fontSize: 13,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_labels.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/context_menu/user.context_menu.dart';
|
||||
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||
import 'package:twonly/src/visual/elements/contact_chip.element.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/views/groups/group_create_select_group_name.view.dart';
|
||||
|
||||
class GroupCreateSelectMembersView extends StatefulWidget {
|
||||
|
|
@ -131,14 +134,21 @@ class _StartNewChatView extends State<GroupCreateSelectMembersView> {
|
|||
),
|
||||
),
|
||||
floatingActionButtonAnimator: FloatingActionButtonAnimator.noAnimation,
|
||||
floatingActionButton: FilledButton.icon(
|
||||
floatingActionButton: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: selectedUsers.isEmpty ? null : submitChanges,
|
||||
label: Text(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FaIcon(FontAwesomeIcons.penToSquare, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.groupId == null
|
||||
? context.lang.next
|
||||
: context.lang.updateGroup,
|
||||
),
|
||||
icon: const FaIcon(FontAwesomeIcons.penToSquare),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
|
|
@ -209,19 +219,32 @@ class _StartNewChatView extends State<GroupCreateSelectMembersView> {
|
|||
return UserContextMenu(
|
||||
key: ValueKey(user.userId),
|
||||
contact: user,
|
||||
child: ListTile(
|
||||
child: ContactLabelsSubtitleBuilder(
|
||||
contactId: user.userId,
|
||||
additionalSubtitle: alreadyInGroup.contains(user.userId)
|
||||
? Text(context.lang.alreadyInGroup)
|
||||
: null,
|
||||
builder: (context, subtitleWidget) {
|
||||
return ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(getContactDisplayName(user)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8,
|
||||
left: 1,
|
||||
),
|
||||
child: VerificationBadgeComp(
|
||||
contact: user,
|
||||
),
|
||||
),
|
||||
FlameCounterWidget(
|
||||
contactId: user.userId,
|
||||
prefix: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: (alreadyInGroup.contains(user.userId))
|
||||
? Text(context.lang.alreadyInGroup)
|
||||
: null,
|
||||
subtitle: subtitleWidget,
|
||||
leading: AvatarIcon(
|
||||
contactId: user.userId,
|
||||
fontSize: 13,
|
||||
|
|
@ -247,6 +270,8 @@ class _StartNewChatView extends State<GroupCreateSelectMembersView> {
|
|||
onTap: () {
|
||||
toggleSelectedUser(user.userId);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
|||
StreamSubscription<int>? _homeViewPageIndexSub;
|
||||
StreamSubscription<NotificationResponse>? _selectNotificationSub;
|
||||
|
||||
static Uri? pendingSharedLink;
|
||||
static final streamHomeViewPageIndex = StreamController<int>.broadcast();
|
||||
static final streamSharedLink = StreamController<Uri>.broadcast();
|
||||
|
||||
|
|
@ -56,7 +57,10 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
|||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
var initialPage = widget.initialPage;
|
||||
if (initialPage == 1 && !userService.currentUser.startWithCameraOpen) {
|
||||
if (HomeViewState.pendingSharedLink != null) {
|
||||
initialPage = 1;
|
||||
} else if (initialPage == 1 &&
|
||||
!userService.currentUser.startWithCameraOpen) {
|
||||
initialPage = 0;
|
||||
}
|
||||
_activePageIdx = initialPage;
|
||||
|
|
@ -100,6 +104,22 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
|||
streamHomeViewPageIndex.add(0);
|
||||
});
|
||||
|
||||
_sharedLinkSub = streamSharedLink.stream.listen((uri) {
|
||||
HomeViewState.pendingSharedLink = null;
|
||||
_mainCameraController.setSharedLinkForPreview(uri);
|
||||
Permission.camera.isGranted.then((hasPermission) {
|
||||
if (hasPermission && mounted) {
|
||||
unawaited(_mainCameraController.selectCamera(0, true));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (HomeViewState.pendingSharedLink != null) {
|
||||
final link = HomeViewState.pendingSharedLink!;
|
||||
HomeViewState.pendingSharedLink = null;
|
||||
_mainCameraController.setSharedLinkForPreview(link);
|
||||
}
|
||||
|
||||
if (initialPage == 1) {
|
||||
Permission.camera.isGranted.then((hasPermission) {
|
||||
if (hasPermission && mounted) {
|
||||
|
|
@ -110,14 +130,11 @@ class HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
|||
|
||||
unawaited(_initAsync());
|
||||
|
||||
_sharedLinkSub = streamSharedLink.stream.listen(
|
||||
_mainCameraController.setSharedLinkForPreview,
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.initialPage == 1 &&
|
||||
!userService.currentUser.startWithCameraOpen ||
|
||||
widget.initialPage == 0) {
|
||||
if (_mainCameraController.sharedLinkForPreview == null &&
|
||||
((widget.initialPage == 1 &&
|
||||
!userService.currentUser.startWithCameraOpen) ||
|
||||
widget.initialPage == 0)) {
|
||||
streamHomeViewPageIndex.add(0);
|
||||
}
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
|||
}
|
||||
|
||||
if (_imageProvider != null) {
|
||||
_imageStream?.removeListener(_listener);
|
||||
_imageStream = null;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
final config = createLocalImageConfiguration(context);
|
||||
|
|
@ -189,11 +191,22 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
|||
gaplessPlayback: true,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
if (error.toString().contains('Invalid image data')) {
|
||||
if (_selectedImageFile != null) {
|
||||
_selectedImageFile?.deleteSync();
|
||||
final fileToDelete = _selectedImageFile;
|
||||
_selectedImageFile = null;
|
||||
if (fileToDelete != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
try {
|
||||
if (fileToDelete.existsSync()) {
|
||||
fileToDelete.deleteSync();
|
||||
}
|
||||
} catch (_) {}
|
||||
if (_retries < 3) {
|
||||
_retries++;
|
||||
_resolveImage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Log.warn(error);
|
||||
return ColoredBox(
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
widget.callbackOnSuccess();
|
||||
} catch (e, stack) {
|
||||
Log.error('Error creating new user', error: e, stackTrace: stack);
|
||||
await deleteLocalUserData();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isTryingToRegister = false;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class _BackupSetupPageState extends State<BackupSetupPage> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'twonly Backup',
|
||||
context.lang.settingsBackup,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:twonly/src/visual/components/avatar_icon.comp.dart'
|
|||
show AvatarIcon;
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
||||
|
||||
class ProfileSetupPage extends StatefulWidget {
|
||||
|
|
@ -67,7 +66,7 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
|
|||
foregroundDecoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: primaryColor,
|
||||
color: context.color.primary,
|
||||
width: 4,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/custom_color_picker_dialog.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/radio_button.element.dart';
|
||||
|
||||
class AppearanceView extends StatefulWidget {
|
||||
|
|
@ -36,7 +37,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
RadioButton<ThemeMode>(
|
||||
value: ThemeMode.system,
|
||||
groupValue: selectedValue,
|
||||
label: 'System default',
|
||||
label: context.lang.themeSystemDefault,
|
||||
onChanged: (value) {
|
||||
selectedValue = value;
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -45,7 +46,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
RadioButton<ThemeMode>(
|
||||
value: ThemeMode.light,
|
||||
groupValue: selectedValue,
|
||||
label: 'Light',
|
||||
label: context.lang.themeLight,
|
||||
onChanged: (value) {
|
||||
selectedValue = value;
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -54,7 +55,7 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
RadioButton<ThemeMode>(
|
||||
value: ThemeMode.dark,
|
||||
groupValue: selectedValue,
|
||||
label: 'Dark',
|
||||
label: context.lang.themeDark,
|
||||
onChanged: (value) {
|
||||
selectedValue = value;
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -72,6 +73,124 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _showSelectPrimaryColor(BuildContext context) async {
|
||||
const presetColors = <Color>[
|
||||
Color(0xFF57CC99), // Original Twonly Green
|
||||
Color(0xFF3A76F0), // Signal Blue
|
||||
Color(0xFF9D4EDD), // Purple
|
||||
Color(0xFFFF5964), // Coral Red
|
||||
Color(0xFFFF9F1C), // Amber Orange
|
||||
];
|
||||
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (context) {
|
||||
final activeColor =
|
||||
context.watch<SettingsChangeProvider>().primaryColor;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 8, 24, 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.settingsAppearancePrimaryColor,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: presetColors.map((color) {
|
||||
final isSelected =
|
||||
activeColor.toARGB32() == color.toARGB32();
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await context
|
||||
.read<SettingsChangeProvider>()
|
||||
.updatePrimaryColor(color);
|
||||
},
|
||||
child: Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withValues(alpha: 0.4),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color:
|
||||
isSelected ? Colors.white : Colors.transparent,
|
||||
width: 3,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? const Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const SweepGradient(
|
||||
colors: [
|
||||
Colors.red,
|
||||
Colors.yellow,
|
||||
Colors.green,
|
||||
Colors.cyan,
|
||||
Colors.blue,
|
||||
Color(0xFFFF00FF),
|
||||
Colors.red,
|
||||
],
|
||||
),
|
||||
border: Border.all(color: Colors.white24, width: 2),
|
||||
),
|
||||
),
|
||||
title: Text(context.lang.customColor),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () async {
|
||||
final pickedValue = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) => CustomColorPickerDialog(
|
||||
initialColor: activeColor,
|
||||
),
|
||||
);
|
||||
if (pickedValue != null && context.mounted) {
|
||||
final pickedColor = Color(pickedValue);
|
||||
await context
|
||||
.read<SettingsChangeProvider>()
|
||||
.updatePrimaryColor(pickedColor);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> toggleShowNewsIcon() async {
|
||||
await UserService.update((u) {
|
||||
u.showNewsShortcut = !u.showNewsShortcut;
|
||||
|
|
@ -90,9 +209,22 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
});
|
||||
}
|
||||
|
||||
String _themeModeLabel(BuildContext context, ThemeMode mode) {
|
||||
switch (mode) {
|
||||
case ThemeMode.system:
|
||||
return context.lang.themeSystemDefault;
|
||||
case ThemeMode.light:
|
||||
return context.lang.themeLight;
|
||||
case ThemeMode.dark:
|
||||
return context.lang.themeDark;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedTheme = context.watch<SettingsChangeProvider>().themeMode;
|
||||
final primaryColor = context.watch<SettingsChangeProvider>().primaryColor;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.settingsAppearance),
|
||||
|
|
@ -105,13 +237,28 @@ class _AppearanceViewState extends State<AppearanceView> {
|
|||
ListTile(
|
||||
title: Text(context.lang.settingsAppearanceTheme),
|
||||
subtitle: Text(
|
||||
selectedTheme.name,
|
||||
_themeModeLabel(context, selectedTheme),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
onTap: () async {
|
||||
await _showSelectThemeMode(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.lang.settingsAppearancePrimaryColor),
|
||||
trailing: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 1.5),
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
await _showSelectPrimaryColor(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.lang.hideNewsIcon),
|
||||
onTap: toggleShowNewsIcon,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/model/json/backup.model.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||
import 'package:twonly/src/services/backup.service.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||
import 'package:twonly/src/services/subscription.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/views/settings/backup/memories_backup_detail.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/backup/passwordless_recovery/components/status.passwordless_recovery.comp.dart';
|
||||
import 'package:twonly/src/visual/views/settings/backup/passwordless_recovery/setup.passwordless_recovery.view.dart';
|
||||
|
||||
|
|
@ -21,6 +31,7 @@ class BackupView extends StatefulWidget {
|
|||
class _BackupViewState extends State<BackupView> {
|
||||
bool _isLoading = false;
|
||||
CurrentBackupStatus? _backupStatus;
|
||||
server.Response_MemoriesUsage? _memoriesUsage;
|
||||
StreamSubscription<void>? _backupUpdateSub;
|
||||
|
||||
@override
|
||||
|
|
@ -41,51 +52,29 @@ class _BackupViewState extends State<BackupView> {
|
|||
Future<void> _loadBackupStatus() async {
|
||||
setState(() => _isLoading = true);
|
||||
final status = await BackupService.getData();
|
||||
final memoriesUsage = await apiService.getMemoriesUsage();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_backupStatus = status;
|
||||
_memoriesUsage = memoriesUsage;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
String _getBackupStatusString(LastBackupUploadState status) {
|
||||
switch (status) {
|
||||
case LastBackupUploadState.none:
|
||||
return context.lang.backupPending;
|
||||
case LastBackupUploadState.pending:
|
||||
return context.lang.backupPending;
|
||||
case LastBackupUploadState.failed:
|
||||
return context.lang.backupFailed;
|
||||
case LastBackupUploadState.success:
|
||||
return context.lang.backupSuccess;
|
||||
String _buildTileSubtitle(DateTime? date, int? size) {
|
||||
if (date == null) return '-';
|
||||
final dateStr = formatRelativeDateTime(context, date);
|
||||
if (size != null && size > 0) {
|
||||
return '$dateStr • ${formatBytes(size)}';
|
||||
}
|
||||
}
|
||||
|
||||
List<TableRow> _buildTableRows(List<(String, String)> rows) {
|
||||
return rows.map((pair) {
|
||||
return TableRow(
|
||||
children: [
|
||||
TableCell(
|
||||
child: Text(pair.$1),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
child: Text(
|
||||
pair.$2,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentPlan = context.watch<PurchasesProvider>().plan;
|
||||
final isFreePlan = currentPlan == SubscriptionPlan.Free;
|
||||
|
||||
return StreamBuilder<void>(
|
||||
stream: userService.onUserUpdated,
|
||||
builder: (context, _) {
|
||||
|
|
@ -94,100 +83,18 @@ class _BackupViewState extends State<BackupView> {
|
|||
title: Text(context.lang.settingsBackup),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.lang.backupTwonlySafeDesc,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (userService.currentUser.passwordLessRecovery != null)
|
||||
const PasswordLessRecoveryStatus(),
|
||||
|
||||
if (userService.currentUser.isBackupEnabled)
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(height: 32),
|
||||
Center(
|
||||
child: Text(
|
||||
context.lang.backupIdentityHeader,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Table(
|
||||
defaultVerticalAlignment:
|
||||
TableCellVerticalAlignment.middle,
|
||||
children: _buildTableRows([
|
||||
(
|
||||
context.lang.backupLastBackupDate,
|
||||
_backupStatus?.identityLastSuccessFull != null
|
||||
? formatDateTime(
|
||||
context,
|
||||
_backupStatus!.identityLastSuccessFull,
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: PasswordLessRecoveryStatus(),
|
||||
)
|
||||
: '-',
|
||||
),
|
||||
(
|
||||
context.lang.backupLastBackupSize,
|
||||
_backupStatus?.identitySize != null
|
||||
? formatBytes(_backupStatus!.identitySize!)
|
||||
: '-',
|
||||
),
|
||||
(
|
||||
context.lang.backupLastBackupResult,
|
||||
_getBackupStatusString(
|
||||
_backupStatus?.identityState ??
|
||||
LastBackupUploadState.none,
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Center(
|
||||
child: Text(
|
||||
context.lang.backupArchiveHeader,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Table(
|
||||
defaultVerticalAlignment:
|
||||
TableCellVerticalAlignment.middle,
|
||||
children: _buildTableRows([
|
||||
(
|
||||
context.lang.backupLastBackupDate,
|
||||
_backupStatus?.archiveLastSuccessFull != null
|
||||
? formatDateTime(
|
||||
context,
|
||||
_backupStatus!.archiveLastSuccessFull,
|
||||
)
|
||||
: '-',
|
||||
),
|
||||
(
|
||||
context.lang.backupLastBackupSize,
|
||||
_backupStatus?.archiveSize != null
|
||||
? formatBytes(_backupStatus!.archiveSize!)
|
||||
: '-',
|
||||
),
|
||||
(
|
||||
context.lang.backupLastBackupResult,
|
||||
_getBackupStatusString(
|
||||
_backupStatus?.archiveState ??
|
||||
LastBackupUploadState.none,
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (userService.currentUser.passwordLessRecovery == null) ...[
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
else
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () =>
|
||||
|
|
@ -195,8 +102,86 @@ class _BackupViewState extends State<BackupView> {
|
|||
child: Text(context.lang.passwordlessRecoveryEnableBtn),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
context.lang.backupTwonlySafeDesc,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: context.color.onSurfaceVariant,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (userService.currentUser.isBackupEnabled) ...[
|
||||
// 1. Identity tile
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.userCheck,
|
||||
text: context.lang.backupIdentityHeader,
|
||||
subtitle: Text(
|
||||
_buildTileSubtitle(
|
||||
_backupStatus?.identityLastSuccessFull,
|
||||
_backupStatus?.identitySize,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 2. Contacts & Messages tile
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.comments,
|
||||
text: context.lang.backupArchiveHeader,
|
||||
subtitle: Text(
|
||||
_buildTileSubtitle(
|
||||
_backupStatus?.archiveLastSuccessFull,
|
||||
_backupStatus?.archiveSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 3. Memories tile
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.photoFilm,
|
||||
text: context.lang.memoriesBackupTitle,
|
||||
subtitle: Text(
|
||||
isFreePlan
|
||||
? context.lang.backupMemoriesUpgradeRequired
|
||||
: (!userService.currentUser.isCloudBackupEnabled
|
||||
? context.lang.backupMemoriesNotEnabled
|
||||
: (_memoriesUsage != null
|
||||
? '${formatBytes(_memoriesUsage!.currentBytes.toInt())} / ${formatBytes(_memoriesUsage!.maxBytes.toInt())}'
|
||||
: '-')),
|
||||
),
|
||||
trailing: Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
onTap: () async {
|
||||
if (isFreePlan) {
|
||||
await context.push(Routes.settingsSubscription);
|
||||
} else if (!userService
|
||||
.currentUser
|
||||
.isCloudBackupEnabled) {
|
||||
await UserService.update(
|
||||
(u) => u.isCloudBackupEnabled = true,
|
||||
);
|
||||
if (mounted) setState(() {});
|
||||
unawaited(memoriesCloudService.checkUploads());
|
||||
} else {
|
||||
await context.navPush(const MemoriesBackupDetailView());
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
||||
Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
@ -212,9 +197,7 @@ class _BackupViewState extends State<BackupView> {
|
|||
_isLoading = true;
|
||||
});
|
||||
await BackupService.makeBackup(force: true);
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
await _loadBackupStatus();
|
||||
},
|
||||
child: Text(context.lang.backupTwonlySaveNow),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class _SetupBackupViewState extends State<SetupBackupView> {
|
|||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('twonly Backup'),
|
||||
title: Text(context.lang.settingsBackup),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => showBackupExplanation(context),
|
||||
|
|
@ -127,12 +127,6 @@ class _SetupBackupViewState extends State<SetupBackupView> {
|
|||
_repeatedController.text.isNotEmpty,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
context.lang.backupNoPasswordRecovery,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ void showBackupExplanation(BuildContext context) {
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'twonly Backup',
|
||||
context.lang.settingsBackup,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,312 @@
|
|||
import 'dart:async';
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage/storage_contents.view.dart';
|
||||
|
||||
class MemoriesBackupDetailView extends StatefulWidget {
|
||||
const MemoriesBackupDetailView({super.key});
|
||||
|
||||
@override
|
||||
State<MemoriesBackupDetailView> createState() =>
|
||||
_MemoriesBackupDetailViewState();
|
||||
}
|
||||
|
||||
class _MemoriesBackupDetailViewState extends State<MemoriesBackupDetailView> {
|
||||
server.Response_MemoriesUsage? _memoriesUsage;
|
||||
int _cloudOnlyCount = 0;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadStats();
|
||||
}
|
||||
|
||||
Future<void> _loadStats() async {
|
||||
final memoriesUsage = await apiService.getMemoriesUsage();
|
||||
final cloudOnlyCount = await twonlyDB.mediaFilesDao
|
||||
.getCloudOnlyMemoriesCount();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_memoriesUsage = memoriesUsage;
|
||||
_cloudOnlyCount = cloudOnlyCount;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disableBackup() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.settingsStorageDisableBackupTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: formattedText(
|
||||
context,
|
||||
context.lang.settingsStorageDisableBackupBody(
|
||||
_cloudOnlyCount,
|
||||
),
|
||||
textColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryMiddle,
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.lang.galleryCancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.errorMiddle,
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(
|
||||
context.lang.settingsStorageDisableBackupBtn,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await apiService.disableMemoriesBackup();
|
||||
final allMedias = await (twonlyDB.select(
|
||||
twonlyDB.mediaFiles,
|
||||
)..where((t) => t.stored.equals(true))).get();
|
||||
for (final media in allMedias) {
|
||||
final ms = MediaFileService(media);
|
||||
if (!ms.storedPath.existsSync()) {
|
||||
ms.fullMediaRemoval();
|
||||
await twonlyDB.mediaFilesDao.deleteMediaFile(media.mediaId);
|
||||
}
|
||||
}
|
||||
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
||||
const MediaFilesCompanion(
|
||||
cloudState: Value(CloudState.none),
|
||||
),
|
||||
);
|
||||
await UserService.update((u) => u.isCloudBackupEnabled = false);
|
||||
if (mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
showSnackbar(context, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.memoriesBackupTitle),
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator.adaptive())
|
||||
: ListView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
children: [
|
||||
Text(
|
||||
_memoriesUsage != null
|
||||
? '${formatBytes(_memoriesUsage!.currentBytes.toInt())} / ${formatBytes(_memoriesUsage!.maxBytes.toInt())}'
|
||||
: '-',
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
height: 24,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (_memoriesUsage == null ||
|
||||
_memoriesUsage!.maxBytes == 0) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final maxWidth = constraints.maxWidth;
|
||||
final current = _memoriesUsage!.currentBytes.toDouble();
|
||||
final max = _memoriesUsage!.maxBytes.toDouble();
|
||||
final usageWidth =
|
||||
((current / max).clamp(0.0, 1.0)) * maxWidth;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (usageWidth > 0)
|
||||
Container(
|
||||
width: usageWidth,
|
||||
color: Colors.blue,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
StreamBuilder<MemoriesBackupProgress?>(
|
||||
initialData: memoriesCloudService.currentProgress,
|
||||
stream: memoriesCloudService.progressStream,
|
||||
builder: (context, snapshot) {
|
||||
final progress = snapshot.data;
|
||||
final isSyncing =
|
||||
progress != null && progress.totalPending > 0;
|
||||
final percent = isSyncing
|
||||
? ((progress.currentUploaded / progress.totalPending) +
|
||||
(progress.currentUploadProgress /
|
||||
progress.totalPending))
|
||||
.clamp(0.0, 1.0)
|
||||
: 0.0;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (isSyncing) ...[
|
||||
Text(
|
||||
'Syncing: ${progress.currentUploaded} / ${progress.totalPending} files (${(percent * 100).toStringAsFixed(1)}%)',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: percent,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () =>
|
||||
context.navPush(const StorageContentsView()),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.folder_open_outlined,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(context.lang.settingsStorageContents),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryDense,
|
||||
onPressed: isSyncing
|
||||
? null
|
||||
: () async {
|
||||
final memories = await twonlyDB
|
||||
.mediaFilesDao
|
||||
.getMemoriesToBackup();
|
||||
if (memories.isEmpty) {
|
||||
if (context.mounted) {
|
||||
showSnackbar(
|
||||
context,
|
||||
context
|
||||
.lang
|
||||
.settingsStorageSyncUpToDate,
|
||||
level: SnackbarLevel.success,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
unawaited(
|
||||
memoriesCloudService.checkUploads(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.sync, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(context.lang.settingsStorageSyncNow),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryDense,
|
||||
onPressed: _disableBackup,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.cloud_off_outlined, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
context
|
||||
.lang
|
||||
.settingsStorageDisableBackupAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ import 'package:flutter/services.dart';
|
|||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class _FactorOption {
|
||||
const _FactorOption({
|
||||
|
|
@ -113,7 +112,7 @@ class SecondFactorPicker extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.transparent,
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Column(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class ThresholdPicker extends StatelessWidget {
|
||||
const ThresholdPicker({
|
||||
|
|
@ -103,7 +102,7 @@ class ThresholdPicker extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor : Colors.transparent,
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: Center(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ class _ChatSettingsViewState extends State<ChatSettingsView> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> setShowRestoreFlame(bool value) async {
|
||||
await UserService.update((u) {
|
||||
u.showRestoreFlame = value;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -55,6 +61,13 @@ class _ChatSettingsViewState extends State<ChatSettingsView> {
|
|||
.automaticallyMarkEqualMediaFilesAsOpened,
|
||||
onChanged: setAutomaticallyMarkEqualMediaFilesAsOpened,
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: Text(
|
||||
context.lang.settingsShowRestoreFlameTitle,
|
||||
),
|
||||
value: userService.currentUser.showRestoreFlame,
|
||||
onChanged: setShowRestoreFlame,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,21 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||
import 'package:twonly/src/services/subscription.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage/storage_contents.view.dart';
|
||||
|
||||
|
|
@ -28,8 +19,6 @@ class ManageStorageView extends StatefulWidget {
|
|||
|
||||
class _ManageStorageViewState extends State<ManageStorageView> {
|
||||
Map<MediaType, int> _stats = {};
|
||||
server.Response_MemoriesUsage? _memoriesUsage;
|
||||
int _cloudOnlyCount = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -39,110 +28,13 @@ class _ManageStorageViewState extends State<ManageStorageView> {
|
|||
|
||||
Future<void> _loadStats() async {
|
||||
final stats = await twonlyDB.mediaFilesDao.getStorageStats();
|
||||
final memoriesUsage = await apiService.getMemoriesUsage();
|
||||
final cloudOnlyCount = await twonlyDB.mediaFilesDao
|
||||
.getCloudOnlyMemoriesCount();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_stats = stats;
|
||||
_memoriesUsage = memoriesUsage;
|
||||
_cloudOnlyCount = cloudOnlyCount;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disableBackup() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.settingsStorageDisableBackupTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: formattedText(
|
||||
context,
|
||||
context.lang.settingsStorageDisableBackupBody(
|
||||
_cloudOnlyCount,
|
||||
),
|
||||
textColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryMiddle,
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.lang.galleryCancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.errorMiddle,
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.lang.settingsStorageDisableBackupBtn),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await apiService.disableMemoriesBackup();
|
||||
final allMedias = await (twonlyDB.select(
|
||||
twonlyDB.mediaFiles,
|
||||
)..where((t) => t.stored.equals(true))).get();
|
||||
for (final media in allMedias) {
|
||||
final ms = MediaFileService(media);
|
||||
if (!ms.storedPath.existsSync()) {
|
||||
ms.fullMediaRemoval();
|
||||
await twonlyDB.mediaFilesDao.deleteMediaFile(media.mediaId);
|
||||
}
|
||||
}
|
||||
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
||||
const MediaFilesCompanion(
|
||||
cloudState: Value(CloudState.none),
|
||||
),
|
||||
);
|
||||
await UserService.update((u) => u.isCloudBackupEnabled = false);
|
||||
await _loadStats();
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
showSnackbar(context, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentPlan = context.watch<PurchasesProvider>().plan;
|
||||
|
|
@ -162,7 +54,7 @@ class _ManageStorageViewState extends State<ManageStorageView> {
|
|||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (isFreePlan) ...[
|
||||
if (isFreePlan || !userService.currentUser.isCloudBackupEnabled) ...[
|
||||
Card(
|
||||
elevation: 0,
|
||||
color: Theme.of(
|
||||
|
|
@ -177,223 +69,44 @@ class _ManageStorageViewState extends State<ManageStorageView> {
|
|||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => context.push(Routes.settingsSubscription),
|
||||
onTap: () {
|
||||
if (isFreePlan) {
|
||||
context.push(Routes.settingsSubscription);
|
||||
} else {
|
||||
context.push(Routes.settingsBackup);
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
Icons.cloud_queue_rounded,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.settingsStorageNoCloudBackupTitle,
|
||||
style: Theme.of(context).textTheme.titleSmall
|
||||
?.copyWith(
|
||||
child: Text(
|
||||
context.lang.backupFreeSpaceWithCloud,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context.lang.settingsStorageNoCloudBackupCard,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
] else ...[
|
||||
Text(
|
||||
context.lang.memoriesBackupTitle,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (!userService.currentUser.isCloudBackupEnabled) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.settingsStorageNoCloudBackupCard,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () async {
|
||||
await UserService.update(
|
||||
(u) => u.isCloudBackupEnabled = true,
|
||||
);
|
||||
setState(() {});
|
||||
unawaited(memoriesCloudService.checkUploads());
|
||||
},
|
||||
child: Text(context.lang.enable),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Divider(),
|
||||
const SizedBox(height: 24),
|
||||
] else ...[
|
||||
Text(
|
||||
_memoriesUsage != null
|
||||
? '${formatBytes(_memoriesUsage!.currentBytes.toInt())} / ${formatBytes(_memoriesUsage!.maxBytes.toInt())}'
|
||||
: '-',
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
height: 24,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (_memoriesUsage == null ||
|
||||
_memoriesUsage!.maxBytes == 0) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final maxWidth = constraints.maxWidth;
|
||||
final current = _memoriesUsage!.currentBytes.toDouble();
|
||||
final max = _memoriesUsage!.maxBytes.toDouble();
|
||||
final usageWidth =
|
||||
((current / max).clamp(0.0, 1.0)) * maxWidth;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (usageWidth > 0)
|
||||
Container(
|
||||
width: usageWidth,
|
||||
color: Colors.blue,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
StreamBuilder<MemoriesBackupProgress?>(
|
||||
initialData: memoriesCloudService.currentProgress,
|
||||
stream: memoriesCloudService.progressStream,
|
||||
builder: (context, snapshot) {
|
||||
final progress = snapshot.data;
|
||||
final isSyncing =
|
||||
progress != null && progress.totalPending > 0;
|
||||
final percent = isSyncing
|
||||
? ((progress.currentUploaded / progress.totalPending) +
|
||||
(progress.currentUploadProgress /
|
||||
progress.totalPending))
|
||||
.clamp(0.0, 1.0)
|
||||
: 0.0;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (isSyncing) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Syncing: ${progress.currentUploaded} / ${progress.totalPending} files (${(percent * 100).toStringAsFixed(1)}%)',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
LinearProgressIndicator(
|
||||
value: percent,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryDense,
|
||||
onPressed: isSyncing
|
||||
? null
|
||||
: () async {
|
||||
final memories = await twonlyDB.mediaFilesDao
|
||||
.getMemoriesToBackup();
|
||||
if (memories.isEmpty) {
|
||||
if (context.mounted) {
|
||||
showSnackbar(
|
||||
context,
|
||||
context
|
||||
.lang
|
||||
.settingsStorageSyncUpToDate,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
unawaited(
|
||||
memoriesCloudService.checkUploads(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.sync, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(context.lang.settingsStorageSyncNow),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryDense,
|
||||
onPressed: _disableBackup,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.cloud_off_outlined, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
context.lang.settingsStorageDisableBackupAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Divider(),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
],
|
||||
Text(
|
||||
isFreePlan
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue