mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 07:44:08 +00:00
start with cloud backup
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
3c26bf55a8
commit
96f8bc39d0
44 changed files with 16898 additions and 351 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit d99dba6a5e0eb13d7dcb38a842e64c23a1779f1c
|
Subproject commit 32882a31904a0c1ffd38974bf5158ceee0f3ecb7
|
||||||
|
|
@ -9,6 +9,24 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
class RustKeyManager {
|
class RustKeyManager {
|
||||||
const RustKeyManager();
|
const RustKeyManager();
|
||||||
|
|
||||||
|
static Future<Uint8List> decryptCloudMediaKey({
|
||||||
|
required List<int> encryptedMediaKey,
|
||||||
|
required String addition,
|
||||||
|
}) => RustLib.instance.api
|
||||||
|
.crateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKey(
|
||||||
|
encryptedMediaKey: encryptedMediaKey,
|
||||||
|
addition: addition,
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<Uint8List> encryptCloudMediaKey({
|
||||||
|
required List<int> mediaKey,
|
||||||
|
required String addition,
|
||||||
|
}) => RustLib.instance.api
|
||||||
|
.crateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKey(
|
||||||
|
mediaKey: mediaKey,
|
||||||
|
addition: addition,
|
||||||
|
);
|
||||||
|
|
||||||
static Future<Uint8List> getLoginToken() => RustLib.instance.api
|
static Future<Uint8List> getLoginToken() => RustLib.instance.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
.crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||||
String get codegenVersion => '2.12.0';
|
String get codegenVersion => '2.12.0';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get rustContentHash => 340781866;
|
int get rustContentHash => 1788847092;
|
||||||
|
|
||||||
static const kDefaultExternalLibraryLoaderConfig =
|
static const kDefaultExternalLibraryLoaderConfig =
|
||||||
ExternalLibraryLoaderConfig(
|
ExternalLibraryLoaderConfig(
|
||||||
|
|
@ -205,6 +205,18 @@ abstract class RustLibApi extends BaseApi {
|
||||||
required String password,
|
required String password,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<Uint8List>
|
||||||
|
crateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKey({
|
||||||
|
required List<int> encryptedMediaKey,
|
||||||
|
required String addition,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Uint8List>
|
||||||
|
crateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKey({
|
||||||
|
required List<int> mediaKey,
|
||||||
|
required String addition,
|
||||||
|
});
|
||||||
|
|
||||||
Future<Uint8List> crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
Future<Uint8List> crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
||||||
|
|
||||||
Future<(Uint8List, PlatformInt64)>
|
Future<(Uint8List, PlatformInt64)>
|
||||||
|
|
@ -1141,6 +1153,82 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
argNames: ["userId", "password"],
|
argNames: ["userId", "password"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List>
|
||||||
|
crateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKey({
|
||||||
|
required List<int> encryptedMediaKey,
|
||||||
|
required String addition,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_list_prim_u_8_loose(encryptedMediaKey, serializer);
|
||||||
|
sse_encode_String(addition, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 18,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
|
constMeta:
|
||||||
|
kCrateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKeyConstMeta,
|
||||||
|
argValues: [encryptedMediaKey, addition],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta
|
||||||
|
get kCrateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKeyConstMeta =>
|
||||||
|
const TaskConstMeta(
|
||||||
|
debugName: "rust_key_manager_decrypt_cloud_media_key",
|
||||||
|
argNames: ["encryptedMediaKey", "addition"],
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Uint8List>
|
||||||
|
crateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKey({
|
||||||
|
required List<int> mediaKey,
|
||||||
|
required String addition,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_list_prim_u_8_loose(mediaKey, serializer);
|
||||||
|
sse_encode_String(addition, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 19,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
|
constMeta:
|
||||||
|
kCrateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKeyConstMeta,
|
||||||
|
argValues: [mediaKey, addition],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta
|
||||||
|
get kCrateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKeyConstMeta =>
|
||||||
|
const TaskConstMeta(
|
||||||
|
debugName: "rust_key_manager_encrypt_cloud_media_key",
|
||||||
|
argNames: ["mediaKey", "addition"],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken() {
|
Future<Uint8List> crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken() {
|
||||||
return handler.executeNormal(
|
return handler.executeNormal(
|
||||||
|
|
@ -1150,7 +1238,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 18,
|
funcId: 20,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1183,7 +1271,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 19,
|
funcId: 21,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1215,7 +1303,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 20,
|
funcId: 22,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1250,7 +1338,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 21,
|
funcId: 23,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1292,7 +1380,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 22,
|
funcId: 24,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1336,7 +1424,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 23,
|
funcId: 25,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1369,7 +1457,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 24,
|
funcId: 26,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1401,7 +1489,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 25,
|
funcId: 27,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1436,7 +1524,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 26,
|
funcId: 28,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1468,7 +1556,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 27,
|
funcId: 29,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1503,7 +1591,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 28,
|
funcId: 30,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1540,7 +1628,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 29,
|
funcId: 31,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1579,7 +1667,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 30,
|
funcId: 32,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1614,7 +1702,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 31,
|
funcId: 33,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1650,7 +1738,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 32,
|
funcId: 34,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1687,7 +1775,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 33,
|
funcId: 35,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1725,7 +1813,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 34,
|
funcId: 36,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1763,7 +1851,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 35,
|
funcId: 37,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1801,7 +1889,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 36,
|
funcId: 38,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1840,7 +1928,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 37,
|
funcId: 39,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1879,7 +1967,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 38,
|
funcId: 40,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1924,7 +2012,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 39,
|
funcId: 41,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1976,7 +2064,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 40,
|
funcId: 42,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2017,7 +2105,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 41,
|
funcId: 43,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2055,7 +2143,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 42,
|
funcId: 44,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2093,7 +2181,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 43,
|
funcId: 45,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2131,7 +2219,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 44,
|
funcId: 46,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2169,7 +2257,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 45,
|
funcId: 47,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2211,7 +2299,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 46,
|
funcId: 48,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -2251,7 +2339,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 47,
|
funcId: 49,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
(t) =>
|
(t) =>
|
||||||
t.stored.equals(true) &
|
t.stored.equals(true) &
|
||||||
(t.storedFileHash.isNull() |
|
(t.storedFileHash.isNull() |
|
||||||
|
t.blurhash.isNull() |
|
||||||
t.hasCropAnalyzed.equals(false) |
|
t.hasCropAnalyzed.equals(false) |
|
||||||
(t.hasThumbnail.equals(false) &
|
(t.hasThumbnail.equals(false) &
|
||||||
t.type.equals(MediaType.audio.name).not()) |
|
t.type.equals(MediaType.audio.name).not()) |
|
||||||
|
|
@ -141,7 +142,12 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
|
|
||||||
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
||||||
final query =
|
final query =
|
||||||
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
|
(select(mediaFiles)..where(
|
||||||
|
(t) =>
|
||||||
|
t.stored.equals(true) |
|
||||||
|
t.cloudState.equals(CloudState.uploaded.name),
|
||||||
|
))
|
||||||
|
.join([])
|
||||||
..groupBy([
|
..groupBy([
|
||||||
const CustomExpression<Object>(
|
const CustomExpression<Object>(
|
||||||
'COALESCE(stored_file_hash, media_id)',
|
'COALESCE(stored_file_hash, media_id)',
|
||||||
|
|
@ -219,4 +225,12 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<MediaFile>> getMemoriesToBackup() async {
|
||||||
|
return (select(mediaFiles)..where(
|
||||||
|
(t) =>
|
||||||
|
t.stored.equals(true) & t.cloudState.equals(CloudState.none.name),
|
||||||
|
))
|
||||||
|
.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3161
lib/src/database/schemas/twonly_db/drift_schema_v23.json
Normal file
3161
lib/src/database/schemas/twonly_db/drift_schema_v23.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -8,6 +8,12 @@ enum MediaType {
|
||||||
audio,
|
audio,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CloudState {
|
||||||
|
none,
|
||||||
|
pending,
|
||||||
|
uploaded,
|
||||||
|
}
|
||||||
|
|
||||||
enum UploadState {
|
enum UploadState {
|
||||||
// Image/Video was taken. A database entry was created to track it...
|
// Image/Video was taken. A database entry was created to track it...
|
||||||
initialized,
|
initialized,
|
||||||
|
|
@ -43,6 +49,9 @@ class MediaFiles extends Table {
|
||||||
TextColumn get type => textEnum<MediaType>()();
|
TextColumn get type => textEnum<MediaType>()();
|
||||||
|
|
||||||
TextColumn get uploadState => textEnum<UploadState>().nullable()();
|
TextColumn get uploadState => textEnum<UploadState>().nullable()();
|
||||||
|
TextColumn get cloudState =>
|
||||||
|
textEnum<CloudState>().withDefault(const Constant('none'))();
|
||||||
|
TextColumn get blurhash => text().nullable()();
|
||||||
TextColumn get downloadState => textEnum<DownloadState>().nullable()();
|
TextColumn get downloadState => textEnum<DownloadState>().nullable()();
|
||||||
|
|
||||||
BoolColumn get requiresAuthentication =>
|
BoolColumn get requiresAuthentication =>
|
||||||
|
|
@ -69,15 +78,13 @@ class MediaFiles extends Table {
|
||||||
|
|
||||||
BlobColumn get storedFileHash => blob().nullable()();
|
BlobColumn get storedFileHash => blob().nullable()();
|
||||||
|
|
||||||
BoolColumn get hasThumbnail =>
|
BoolColumn get hasThumbnail => boolean().withDefault(const Constant(false))();
|
||||||
boolean().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
IntColumn get sizeInBytes => integer().nullable()();
|
IntColumn get sizeInBytes => integer().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
TextColumn get createdAtMonth => text().nullable()();
|
TextColumn get createdAtMonth => text().nullable()();
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {mediaId};
|
Set<Column> get primaryKey => {mediaId};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 22;
|
int get schemaVersion => 23;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
final connection = driftDatabase(
|
final connection = driftDatabase(
|
||||||
|
|
@ -275,6 +275,16 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
schema.contacts.recoveryContactsThreshold,
|
schema.contacts.recoveryContactsThreshold,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
from22To23: (m, schema) async {
|
||||||
|
await m.addColumn(
|
||||||
|
schema.mediaFiles,
|
||||||
|
schema.mediaFiles.cloudState,
|
||||||
|
);
|
||||||
|
await m.addColumn(
|
||||||
|
schema.mediaFiles,
|
||||||
|
schema.mediaFiles.blurhash,
|
||||||
|
);
|
||||||
|
},
|
||||||
)(m, from, to);
|
)(m, from, to);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3094,6 +3094,27 @@ class $MediaFilesTable extends MediaFiles
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
).withConverter<UploadState?>($MediaFilesTable.$converteruploadStaten);
|
).withConverter<UploadState?>($MediaFilesTable.$converteruploadStaten);
|
||||||
@override
|
@override
|
||||||
|
late final GeneratedColumnWithTypeConverter<CloudState, String> cloudState =
|
||||||
|
GeneratedColumn<String>(
|
||||||
|
'cloud_state',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const Constant('none'),
|
||||||
|
).withConverter<CloudState>($MediaFilesTable.$convertercloudState);
|
||||||
|
static const VerificationMeta _blurhashMeta = const VerificationMeta(
|
||||||
|
'blurhash',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> blurhash = GeneratedColumn<String>(
|
||||||
|
'blurhash',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
@override
|
||||||
late final GeneratedColumnWithTypeConverter<DownloadState?, String>
|
late final GeneratedColumnWithTypeConverter<DownloadState?, String>
|
||||||
downloadState = GeneratedColumn<String>(
|
downloadState = GeneratedColumn<String>(
|
||||||
'download_state',
|
'download_state',
|
||||||
|
|
@ -3333,6 +3354,8 @@ class $MediaFilesTable extends MediaFiles
|
||||||
mediaId,
|
mediaId,
|
||||||
type,
|
type,
|
||||||
uploadState,
|
uploadState,
|
||||||
|
cloudState,
|
||||||
|
blurhash,
|
||||||
downloadState,
|
downloadState,
|
||||||
requiresAuthentication,
|
requiresAuthentication,
|
||||||
stored,
|
stored,
|
||||||
|
|
@ -3373,6 +3396,12 @@ class $MediaFilesTable extends MediaFiles
|
||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_mediaIdMeta);
|
context.missing(_mediaIdMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('blurhash')) {
|
||||||
|
context.handle(
|
||||||
|
_blurhashMeta,
|
||||||
|
blurhash.isAcceptableOrUnknown(data['blurhash']!, _blurhashMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (data.containsKey('requires_authentication')) {
|
if (data.containsKey('requires_authentication')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_requiresAuthenticationMeta,
|
_requiresAuthenticationMeta,
|
||||||
|
|
@ -3542,6 +3571,16 @@ class $MediaFilesTable extends MediaFiles
|
||||||
data['${effectivePrefix}upload_state'],
|
data['${effectivePrefix}upload_state'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
cloudState: $MediaFilesTable.$convertercloudState.fromSql(
|
||||||
|
attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}cloud_state'],
|
||||||
|
)!,
|
||||||
|
),
|
||||||
|
blurhash: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}blurhash'],
|
||||||
|
),
|
||||||
downloadState: $MediaFilesTable.$converterdownloadStaten.fromSql(
|
downloadState: $MediaFilesTable.$converterdownloadStaten.fromSql(
|
||||||
attachedDatabase.typeMapping.read(
|
attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
|
|
@ -3637,6 +3676,8 @@ class $MediaFilesTable extends MediaFiles
|
||||||
const EnumNameConverter<UploadState>(UploadState.values);
|
const EnumNameConverter<UploadState>(UploadState.values);
|
||||||
static JsonTypeConverter2<UploadState?, String?, String?>
|
static JsonTypeConverter2<UploadState?, String?, String?>
|
||||||
$converteruploadStaten = JsonTypeConverter2.asNullable($converteruploadState);
|
$converteruploadStaten = JsonTypeConverter2.asNullable($converteruploadState);
|
||||||
|
static JsonTypeConverter2<CloudState, String, String> $convertercloudState =
|
||||||
|
const EnumNameConverter<CloudState>(CloudState.values);
|
||||||
static JsonTypeConverter2<DownloadState, String, String>
|
static JsonTypeConverter2<DownloadState, String, String>
|
||||||
$converterdownloadState = const EnumNameConverter<DownloadState>(
|
$converterdownloadState = const EnumNameConverter<DownloadState>(
|
||||||
DownloadState.values,
|
DownloadState.values,
|
||||||
|
|
@ -3655,6 +3696,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
final String mediaId;
|
final String mediaId;
|
||||||
final MediaType type;
|
final MediaType type;
|
||||||
final UploadState? uploadState;
|
final UploadState? uploadState;
|
||||||
|
final CloudState cloudState;
|
||||||
|
final String? blurhash;
|
||||||
final DownloadState? downloadState;
|
final DownloadState? downloadState;
|
||||||
final bool requiresAuthentication;
|
final bool requiresAuthentication;
|
||||||
final bool stored;
|
final bool stored;
|
||||||
|
|
@ -3678,6 +3721,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
required this.mediaId,
|
required this.mediaId,
|
||||||
required this.type,
|
required this.type,
|
||||||
this.uploadState,
|
this.uploadState,
|
||||||
|
required this.cloudState,
|
||||||
|
this.blurhash,
|
||||||
this.downloadState,
|
this.downloadState,
|
||||||
required this.requiresAuthentication,
|
required this.requiresAuthentication,
|
||||||
required this.stored,
|
required this.stored,
|
||||||
|
|
@ -3712,6 +3757,14 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
$MediaFilesTable.$converteruploadStaten.toSql(uploadState),
|
$MediaFilesTable.$converteruploadStaten.toSql(uploadState),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
map['cloud_state'] = Variable<String>(
|
||||||
|
$MediaFilesTable.$convertercloudState.toSql(cloudState),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || blurhash != null) {
|
||||||
|
map['blurhash'] = Variable<String>(blurhash);
|
||||||
|
}
|
||||||
if (!nullToAbsent || downloadState != null) {
|
if (!nullToAbsent || downloadState != null) {
|
||||||
map['download_state'] = Variable<String>(
|
map['download_state'] = Variable<String>(
|
||||||
$MediaFilesTable.$converterdownloadStaten.toSql(downloadState),
|
$MediaFilesTable.$converterdownloadStaten.toSql(downloadState),
|
||||||
|
|
@ -3773,6 +3826,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
uploadState: uploadState == null && nullToAbsent
|
uploadState: uploadState == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(uploadState),
|
: Value(uploadState),
|
||||||
|
cloudState: Value(cloudState),
|
||||||
|
blurhash: blurhash == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(blurhash),
|
||||||
downloadState: downloadState == null && nullToAbsent
|
downloadState: downloadState == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(downloadState),
|
: Value(downloadState),
|
||||||
|
|
@ -3833,6 +3890,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
uploadState: $MediaFilesTable.$converteruploadStaten.fromJson(
|
uploadState: $MediaFilesTable.$converteruploadStaten.fromJson(
|
||||||
serializer.fromJson<String?>(json['uploadState']),
|
serializer.fromJson<String?>(json['uploadState']),
|
||||||
),
|
),
|
||||||
|
cloudState: $MediaFilesTable.$convertercloudState.fromJson(
|
||||||
|
serializer.fromJson<String>(json['cloudState']),
|
||||||
|
),
|
||||||
|
blurhash: serializer.fromJson<String?>(json['blurhash']),
|
||||||
downloadState: $MediaFilesTable.$converterdownloadStaten.fromJson(
|
downloadState: $MediaFilesTable.$converterdownloadStaten.fromJson(
|
||||||
serializer.fromJson<String?>(json['downloadState']),
|
serializer.fromJson<String?>(json['downloadState']),
|
||||||
),
|
),
|
||||||
|
|
@ -3875,6 +3936,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
'uploadState': serializer.toJson<String?>(
|
'uploadState': serializer.toJson<String?>(
|
||||||
$MediaFilesTable.$converteruploadStaten.toJson(uploadState),
|
$MediaFilesTable.$converteruploadStaten.toJson(uploadState),
|
||||||
),
|
),
|
||||||
|
'cloudState': serializer.toJson<String>(
|
||||||
|
$MediaFilesTable.$convertercloudState.toJson(cloudState),
|
||||||
|
),
|
||||||
|
'blurhash': serializer.toJson<String?>(blurhash),
|
||||||
'downloadState': serializer.toJson<String?>(
|
'downloadState': serializer.toJson<String?>(
|
||||||
$MediaFilesTable.$converterdownloadStaten.toJson(downloadState),
|
$MediaFilesTable.$converterdownloadStaten.toJson(downloadState),
|
||||||
),
|
),
|
||||||
|
|
@ -3905,6 +3970,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
String? mediaId,
|
String? mediaId,
|
||||||
MediaType? type,
|
MediaType? type,
|
||||||
Value<UploadState?> uploadState = const Value.absent(),
|
Value<UploadState?> uploadState = const Value.absent(),
|
||||||
|
CloudState? cloudState,
|
||||||
|
Value<String?> blurhash = const Value.absent(),
|
||||||
Value<DownloadState?> downloadState = const Value.absent(),
|
Value<DownloadState?> downloadState = const Value.absent(),
|
||||||
bool? requiresAuthentication,
|
bool? requiresAuthentication,
|
||||||
bool? stored,
|
bool? stored,
|
||||||
|
|
@ -3928,6 +3995,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
mediaId: mediaId ?? this.mediaId,
|
mediaId: mediaId ?? this.mediaId,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
uploadState: uploadState.present ? uploadState.value : this.uploadState,
|
uploadState: uploadState.present ? uploadState.value : this.uploadState,
|
||||||
|
cloudState: cloudState ?? this.cloudState,
|
||||||
|
blurhash: blurhash.present ? blurhash.value : this.blurhash,
|
||||||
downloadState: downloadState.present
|
downloadState: downloadState.present
|
||||||
? downloadState.value
|
? downloadState.value
|
||||||
: this.downloadState,
|
: this.downloadState,
|
||||||
|
|
@ -3976,6 +4045,10 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
uploadState: data.uploadState.present
|
uploadState: data.uploadState.present
|
||||||
? data.uploadState.value
|
? data.uploadState.value
|
||||||
: this.uploadState,
|
: this.uploadState,
|
||||||
|
cloudState: data.cloudState.present
|
||||||
|
? data.cloudState.value
|
||||||
|
: this.cloudState,
|
||||||
|
blurhash: data.blurhash.present ? data.blurhash.value : this.blurhash,
|
||||||
downloadState: data.downloadState.present
|
downloadState: data.downloadState.present
|
||||||
? data.downloadState.value
|
? data.downloadState.value
|
||||||
: this.downloadState,
|
: this.downloadState,
|
||||||
|
|
@ -4038,6 +4111,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
..write('mediaId: $mediaId, ')
|
..write('mediaId: $mediaId, ')
|
||||||
..write('type: $type, ')
|
..write('type: $type, ')
|
||||||
..write('uploadState: $uploadState, ')
|
..write('uploadState: $uploadState, ')
|
||||||
|
..write('cloudState: $cloudState, ')
|
||||||
|
..write('blurhash: $blurhash, ')
|
||||||
..write('downloadState: $downloadState, ')
|
..write('downloadState: $downloadState, ')
|
||||||
..write('requiresAuthentication: $requiresAuthentication, ')
|
..write('requiresAuthentication: $requiresAuthentication, ')
|
||||||
..write('stored: $stored, ')
|
..write('stored: $stored, ')
|
||||||
|
|
@ -4066,6 +4141,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
mediaId,
|
mediaId,
|
||||||
type,
|
type,
|
||||||
uploadState,
|
uploadState,
|
||||||
|
cloudState,
|
||||||
|
blurhash,
|
||||||
downloadState,
|
downloadState,
|
||||||
requiresAuthentication,
|
requiresAuthentication,
|
||||||
stored,
|
stored,
|
||||||
|
|
@ -4093,6 +4170,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
other.mediaId == this.mediaId &&
|
other.mediaId == this.mediaId &&
|
||||||
other.type == this.type &&
|
other.type == this.type &&
|
||||||
other.uploadState == this.uploadState &&
|
other.uploadState == this.uploadState &&
|
||||||
|
other.cloudState == this.cloudState &&
|
||||||
|
other.blurhash == this.blurhash &&
|
||||||
other.downloadState == this.downloadState &&
|
other.downloadState == this.downloadState &&
|
||||||
other.requiresAuthentication == this.requiresAuthentication &&
|
other.requiresAuthentication == this.requiresAuthentication &&
|
||||||
other.stored == this.stored &&
|
other.stored == this.stored &&
|
||||||
|
|
@ -4124,6 +4203,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
final Value<String> mediaId;
|
final Value<String> mediaId;
|
||||||
final Value<MediaType> type;
|
final Value<MediaType> type;
|
||||||
final Value<UploadState?> uploadState;
|
final Value<UploadState?> uploadState;
|
||||||
|
final Value<CloudState> cloudState;
|
||||||
|
final Value<String?> blurhash;
|
||||||
final Value<DownloadState?> downloadState;
|
final Value<DownloadState?> downloadState;
|
||||||
final Value<bool> requiresAuthentication;
|
final Value<bool> requiresAuthentication;
|
||||||
final Value<bool> stored;
|
final Value<bool> stored;
|
||||||
|
|
@ -4148,6 +4229,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
this.mediaId = const Value.absent(),
|
this.mediaId = const Value.absent(),
|
||||||
this.type = const Value.absent(),
|
this.type = const Value.absent(),
|
||||||
this.uploadState = const Value.absent(),
|
this.uploadState = const Value.absent(),
|
||||||
|
this.cloudState = const Value.absent(),
|
||||||
|
this.blurhash = const Value.absent(),
|
||||||
this.downloadState = const Value.absent(),
|
this.downloadState = const Value.absent(),
|
||||||
this.requiresAuthentication = const Value.absent(),
|
this.requiresAuthentication = const Value.absent(),
|
||||||
this.stored = const Value.absent(),
|
this.stored = const Value.absent(),
|
||||||
|
|
@ -4173,6 +4256,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
required String mediaId,
|
required String mediaId,
|
||||||
required MediaType type,
|
required MediaType type,
|
||||||
this.uploadState = const Value.absent(),
|
this.uploadState = const Value.absent(),
|
||||||
|
this.cloudState = const Value.absent(),
|
||||||
|
this.blurhash = const Value.absent(),
|
||||||
this.downloadState = const Value.absent(),
|
this.downloadState = const Value.absent(),
|
||||||
this.requiresAuthentication = const Value.absent(),
|
this.requiresAuthentication = const Value.absent(),
|
||||||
this.stored = const Value.absent(),
|
this.stored = const Value.absent(),
|
||||||
|
|
@ -4199,6 +4284,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
Expression<String>? mediaId,
|
Expression<String>? mediaId,
|
||||||
Expression<String>? type,
|
Expression<String>? type,
|
||||||
Expression<String>? uploadState,
|
Expression<String>? uploadState,
|
||||||
|
Expression<String>? cloudState,
|
||||||
|
Expression<String>? blurhash,
|
||||||
Expression<String>? downloadState,
|
Expression<String>? downloadState,
|
||||||
Expression<bool>? requiresAuthentication,
|
Expression<bool>? requiresAuthentication,
|
||||||
Expression<bool>? stored,
|
Expression<bool>? stored,
|
||||||
|
|
@ -4224,6 +4311,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
if (mediaId != null) 'media_id': mediaId,
|
if (mediaId != null) 'media_id': mediaId,
|
||||||
if (type != null) 'type': type,
|
if (type != null) 'type': type,
|
||||||
if (uploadState != null) 'upload_state': uploadState,
|
if (uploadState != null) 'upload_state': uploadState,
|
||||||
|
if (cloudState != null) 'cloud_state': cloudState,
|
||||||
|
if (blurhash != null) 'blurhash': blurhash,
|
||||||
if (downloadState != null) 'download_state': downloadState,
|
if (downloadState != null) 'download_state': downloadState,
|
||||||
if (requiresAuthentication != null)
|
if (requiresAuthentication != null)
|
||||||
'requires_authentication': requiresAuthentication,
|
'requires_authentication': requiresAuthentication,
|
||||||
|
|
@ -4255,6 +4344,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
Value<String>? mediaId,
|
Value<String>? mediaId,
|
||||||
Value<MediaType>? type,
|
Value<MediaType>? type,
|
||||||
Value<UploadState?>? uploadState,
|
Value<UploadState?>? uploadState,
|
||||||
|
Value<CloudState>? cloudState,
|
||||||
|
Value<String?>? blurhash,
|
||||||
Value<DownloadState?>? downloadState,
|
Value<DownloadState?>? downloadState,
|
||||||
Value<bool>? requiresAuthentication,
|
Value<bool>? requiresAuthentication,
|
||||||
Value<bool>? stored,
|
Value<bool>? stored,
|
||||||
|
|
@ -4280,6 +4371,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
mediaId: mediaId ?? this.mediaId,
|
mediaId: mediaId ?? this.mediaId,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
uploadState: uploadState ?? this.uploadState,
|
uploadState: uploadState ?? this.uploadState,
|
||||||
|
cloudState: cloudState ?? this.cloudState,
|
||||||
|
blurhash: blurhash ?? this.blurhash,
|
||||||
downloadState: downloadState ?? this.downloadState,
|
downloadState: downloadState ?? this.downloadState,
|
||||||
requiresAuthentication:
|
requiresAuthentication:
|
||||||
requiresAuthentication ?? this.requiresAuthentication,
|
requiresAuthentication ?? this.requiresAuthentication,
|
||||||
|
|
@ -4322,6 +4415,14 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
$MediaFilesTable.$converteruploadStaten.toSql(uploadState.value),
|
$MediaFilesTable.$converteruploadStaten.toSql(uploadState.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (cloudState.present) {
|
||||||
|
map['cloud_state'] = Variable<String>(
|
||||||
|
$MediaFilesTable.$convertercloudState.toSql(cloudState.value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (blurhash.present) {
|
||||||
|
map['blurhash'] = Variable<String>(blurhash.value);
|
||||||
|
}
|
||||||
if (downloadState.present) {
|
if (downloadState.present) {
|
||||||
map['download_state'] = Variable<String>(
|
map['download_state'] = Variable<String>(
|
||||||
$MediaFilesTable.$converterdownloadStaten.toSql(downloadState.value),
|
$MediaFilesTable.$converterdownloadStaten.toSql(downloadState.value),
|
||||||
|
|
@ -4403,6 +4504,8 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
..write('mediaId: $mediaId, ')
|
..write('mediaId: $mediaId, ')
|
||||||
..write('type: $type, ')
|
..write('type: $type, ')
|
||||||
..write('uploadState: $uploadState, ')
|
..write('uploadState: $uploadState, ')
|
||||||
|
..write('cloudState: $cloudState, ')
|
||||||
|
..write('blurhash: $blurhash, ')
|
||||||
..write('downloadState: $downloadState, ')
|
..write('downloadState: $downloadState, ')
|
||||||
..write('requiresAuthentication: $requiresAuthentication, ')
|
..write('requiresAuthentication: $requiresAuthentication, ')
|
||||||
..write('stored: $stored, ')
|
..write('stored: $stored, ')
|
||||||
|
|
@ -16080,6 +16183,8 @@ typedef $$MediaFilesTableCreateCompanionBuilder =
|
||||||
required String mediaId,
|
required String mediaId,
|
||||||
required MediaType type,
|
required MediaType type,
|
||||||
Value<UploadState?> uploadState,
|
Value<UploadState?> uploadState,
|
||||||
|
Value<CloudState> cloudState,
|
||||||
|
Value<String?> blurhash,
|
||||||
Value<DownloadState?> downloadState,
|
Value<DownloadState?> downloadState,
|
||||||
Value<bool> requiresAuthentication,
|
Value<bool> requiresAuthentication,
|
||||||
Value<bool> stored,
|
Value<bool> stored,
|
||||||
|
|
@ -16106,6 +16211,8 @@ typedef $$MediaFilesTableUpdateCompanionBuilder =
|
||||||
Value<String> mediaId,
|
Value<String> mediaId,
|
||||||
Value<MediaType> type,
|
Value<MediaType> type,
|
||||||
Value<UploadState?> uploadState,
|
Value<UploadState?> uploadState,
|
||||||
|
Value<CloudState> cloudState,
|
||||||
|
Value<String?> blurhash,
|
||||||
Value<DownloadState?> downloadState,
|
Value<DownloadState?> downloadState,
|
||||||
Value<bool> requiresAuthentication,
|
Value<bool> requiresAuthentication,
|
||||||
Value<bool> stored,
|
Value<bool> stored,
|
||||||
|
|
@ -16177,6 +16284,17 @@ class $$MediaFilesTableFilterComposer
|
||||||
builder: (column) => ColumnWithTypeConverterFilters(column),
|
builder: (column) => ColumnWithTypeConverterFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnWithTypeConverterFilters<CloudState, CloudState, String>
|
||||||
|
get cloudState => $composableBuilder(
|
||||||
|
column: $table.cloudState,
|
||||||
|
builder: (column) => ColumnWithTypeConverterFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnFilters<String> get blurhash => $composableBuilder(
|
||||||
|
column: $table.blurhash,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnWithTypeConverterFilters<DownloadState?, DownloadState, String>
|
ColumnWithTypeConverterFilters<DownloadState?, DownloadState, String>
|
||||||
get downloadState => $composableBuilder(
|
get downloadState => $composableBuilder(
|
||||||
column: $table.downloadState,
|
column: $table.downloadState,
|
||||||
|
|
@ -16324,6 +16442,16 @@ class $$MediaFilesTableOrderingComposer
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get cloudState => $composableBuilder(
|
||||||
|
column: $table.cloudState,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<String> get blurhash => $composableBuilder(
|
||||||
|
column: $table.blurhash,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnOrderings<String> get downloadState => $composableBuilder(
|
ColumnOrderings<String> get downloadState => $composableBuilder(
|
||||||
column: $table.downloadState,
|
column: $table.downloadState,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
|
@ -16441,6 +16569,15 @@ class $$MediaFilesTableAnnotationComposer
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
GeneratedColumnWithTypeConverter<CloudState, String> get cloudState =>
|
||||||
|
$composableBuilder(
|
||||||
|
column: $table.cloudState,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get blurhash =>
|
||||||
|
$composableBuilder(column: $table.blurhash, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumnWithTypeConverter<DownloadState?, String> get downloadState =>
|
GeneratedColumnWithTypeConverter<DownloadState?, String> get downloadState =>
|
||||||
$composableBuilder(
|
$composableBuilder(
|
||||||
column: $table.downloadState,
|
column: $table.downloadState,
|
||||||
|
|
@ -16591,6 +16728,8 @@ class $$MediaFilesTableTableManager
|
||||||
Value<String> mediaId = const Value.absent(),
|
Value<String> mediaId = const Value.absent(),
|
||||||
Value<MediaType> type = const Value.absent(),
|
Value<MediaType> type = const Value.absent(),
|
||||||
Value<UploadState?> uploadState = const Value.absent(),
|
Value<UploadState?> uploadState = const Value.absent(),
|
||||||
|
Value<CloudState> cloudState = const Value.absent(),
|
||||||
|
Value<String?> blurhash = const Value.absent(),
|
||||||
Value<DownloadState?> downloadState = const Value.absent(),
|
Value<DownloadState?> downloadState = const Value.absent(),
|
||||||
Value<bool> requiresAuthentication = const Value.absent(),
|
Value<bool> requiresAuthentication = const Value.absent(),
|
||||||
Value<bool> stored = const Value.absent(),
|
Value<bool> stored = const Value.absent(),
|
||||||
|
|
@ -16615,6 +16754,8 @@ class $$MediaFilesTableTableManager
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
type: type,
|
type: type,
|
||||||
uploadState: uploadState,
|
uploadState: uploadState,
|
||||||
|
cloudState: cloudState,
|
||||||
|
blurhash: blurhash,
|
||||||
downloadState: downloadState,
|
downloadState: downloadState,
|
||||||
requiresAuthentication: requiresAuthentication,
|
requiresAuthentication: requiresAuthentication,
|
||||||
stored: stored,
|
stored: stored,
|
||||||
|
|
@ -16641,6 +16782,8 @@ class $$MediaFilesTableTableManager
|
||||||
required String mediaId,
|
required String mediaId,
|
||||||
required MediaType type,
|
required MediaType type,
|
||||||
Value<UploadState?> uploadState = const Value.absent(),
|
Value<UploadState?> uploadState = const Value.absent(),
|
||||||
|
Value<CloudState> cloudState = const Value.absent(),
|
||||||
|
Value<String?> blurhash = const Value.absent(),
|
||||||
Value<DownloadState?> downloadState = const Value.absent(),
|
Value<DownloadState?> downloadState = const Value.absent(),
|
||||||
Value<bool> requiresAuthentication = const Value.absent(),
|
Value<bool> requiresAuthentication = const Value.absent(),
|
||||||
Value<bool> stored = const Value.absent(),
|
Value<bool> stored = const Value.absent(),
|
||||||
|
|
@ -16665,6 +16808,8 @@ class $$MediaFilesTableTableManager
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
type: type,
|
type: type,
|
||||||
uploadState: uploadState,
|
uploadState: uploadState,
|
||||||
|
cloudState: cloudState,
|
||||||
|
blurhash: blurhash,
|
||||||
downloadState: downloadState,
|
downloadState: downloadState,
|
||||||
requiresAuthentication: requiresAuthentication,
|
requiresAuthentication: requiresAuthentication,
|
||||||
stored: stored,
|
stored: stored,
|
||||||
|
|
|
||||||
|
|
@ -11600,6 +11600,539 @@ i1.GeneratedColumn<int> _column_254(String aliasedName) =>
|
||||||
type: i1.DriftSqlType.int,
|
type: i1.DriftSqlType.int,
|
||||||
$customConstraints: 'NULL',
|
$customConstraints: 'NULL',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final class Schema23 extends i0.VersionedSchema {
|
||||||
|
Schema23({required super.database}) : super(version: 23);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
contacts,
|
||||||
|
groups,
|
||||||
|
mediaFiles,
|
||||||
|
messages,
|
||||||
|
messageHistories,
|
||||||
|
reactions,
|
||||||
|
groupMembers,
|
||||||
|
receipts,
|
||||||
|
receivedReceipts,
|
||||||
|
signalIdentityKeyStores,
|
||||||
|
signalPreKeyStores,
|
||||||
|
signalSenderKeyStores,
|
||||||
|
signalSessionStores,
|
||||||
|
signalSignedPreKeyStores,
|
||||||
|
messageActions,
|
||||||
|
groupHistories,
|
||||||
|
keyVerifications,
|
||||||
|
verificationTokens,
|
||||||
|
userDiscoveryAnnouncedUsers,
|
||||||
|
userDiscoveryUserRelations,
|
||||||
|
userDiscoveryOtherPromotions,
|
||||||
|
userDiscoveryOwnPromotions,
|
||||||
|
userDiscoveryShares,
|
||||||
|
shortcuts,
|
||||||
|
shortcutMembers,
|
||||||
|
];
|
||||||
|
late final Shape57 contacts = Shape57(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'contacts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_106,
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_112,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_211,
|
||||||
|
_column_212,
|
||||||
|
_column_213,
|
||||||
|
_column_249,
|
||||||
|
_column_250,
|
||||||
|
_column_251,
|
||||||
|
_column_252,
|
||||||
|
_column_253,
|
||||||
|
_column_254,
|
||||||
|
_column_247,
|
||||||
|
_column_214,
|
||||||
|
_column_215,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape23 groups = Shape23(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'groups',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_119,
|
||||||
|
_column_120,
|
||||||
|
_column_121,
|
||||||
|
_column_122,
|
||||||
|
_column_123,
|
||||||
|
_column_124,
|
||||||
|
_column_125,
|
||||||
|
_column_126,
|
||||||
|
_column_127,
|
||||||
|
_column_128,
|
||||||
|
_column_129,
|
||||||
|
_column_130,
|
||||||
|
_column_131,
|
||||||
|
_column_132,
|
||||||
|
_column_133,
|
||||||
|
_column_134,
|
||||||
|
_column_118,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_137,
|
||||||
|
_column_138,
|
||||||
|
_column_139,
|
||||||
|
_column_140,
|
||||||
|
_column_141,
|
||||||
|
_column_142,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape58 mediaFiles = Shape58(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'media_files',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(media_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_143,
|
||||||
|
_column_144,
|
||||||
|
_column_145,
|
||||||
|
_column_255,
|
||||||
|
_column_256,
|
||||||
|
_column_146,
|
||||||
|
_column_147,
|
||||||
|
_column_148,
|
||||||
|
_column_149,
|
||||||
|
_column_239,
|
||||||
|
_column_240,
|
||||||
|
_column_207,
|
||||||
|
_column_150,
|
||||||
|
_column_151,
|
||||||
|
_column_152,
|
||||||
|
_column_153,
|
||||||
|
_column_154,
|
||||||
|
_column_155,
|
||||||
|
_column_156,
|
||||||
|
_column_157,
|
||||||
|
_column_244,
|
||||||
|
_column_245,
|
||||||
|
_column_118,
|
||||||
|
_column_241,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape25 messages = Shape25(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'messages',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_158,
|
||||||
|
_column_159,
|
||||||
|
_column_160,
|
||||||
|
_column_144,
|
||||||
|
_column_161,
|
||||||
|
_column_162,
|
||||||
|
_column_163,
|
||||||
|
_column_164,
|
||||||
|
_column_165,
|
||||||
|
_column_153,
|
||||||
|
_column_166,
|
||||||
|
_column_167,
|
||||||
|
_column_168,
|
||||||
|
_column_169,
|
||||||
|
_column_118,
|
||||||
|
_column_170,
|
||||||
|
_column_171,
|
||||||
|
_column_172,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape26 messageHistories = Shape26(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_173,
|
||||||
|
_column_174,
|
||||||
|
_column_175,
|
||||||
|
_column_161,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape27 reactions = Shape27(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'reactions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
|
||||||
|
columns: [_column_174, _column_176, _column_177, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape38 groupMembers = Shape38(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_158,
|
||||||
|
_column_178,
|
||||||
|
_column_179,
|
||||||
|
_column_180,
|
||||||
|
_column_209,
|
||||||
|
_column_210,
|
||||||
|
_column_181,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape37 receipts = Shape37(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'receipts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_182,
|
||||||
|
_column_183,
|
||||||
|
_column_184,
|
||||||
|
_column_185,
|
||||||
|
_column_186,
|
||||||
|
_column_208,
|
||||||
|
_column_187,
|
||||||
|
_column_188,
|
||||||
|
_column_189,
|
||||||
|
_column_190,
|
||||||
|
_column_191,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape30 receivedReceipts = Shape30(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'received_receipts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||||
|
columns: [_column_182, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape31 signalIdentityKeyStores = Shape31(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_identity_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||||
|
columns: [_column_192, _column_193, _column_194, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape32 signalPreKeyStores = Shape32(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_pre_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
|
||||||
|
columns: [_column_195, _column_196, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape11 signalSenderKeyStores = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_sender_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
|
||||||
|
columns: [_column_197, _column_198],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape33 signalSessionStores = Shape33(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_session_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||||
|
columns: [_column_192, _column_193, _column_199, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape50 signalSignedPreKeyStores = Shape50(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_signed_pre_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(signed_pre_key_id)'],
|
||||||
|
columns: [_column_242, _column_243, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape34 messageActions = Shape34(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_actions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
|
||||||
|
columns: [_column_174, _column_183, _column_144, _column_200],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape35 groupHistories = Shape35(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_history_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_201,
|
||||||
|
_column_158,
|
||||||
|
_column_202,
|
||||||
|
_column_203,
|
||||||
|
_column_204,
|
||||||
|
_column_205,
|
||||||
|
_column_206,
|
||||||
|
_column_144,
|
||||||
|
_column_200,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape54 keyVerifications = Shape54(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'key_verifications',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_216,
|
||||||
|
_column_183,
|
||||||
|
_column_144,
|
||||||
|
_column_248,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape41 verificationTokens = Shape41(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'verification_tokens',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_217, _column_218, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape52 userDiscoveryAnnouncedUsers = Shape52(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_announced_users',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_219,
|
||||||
|
_column_220,
|
||||||
|
_column_221,
|
||||||
|
_column_222,
|
||||||
|
_column_223,
|
||||||
|
_column_224,
|
||||||
|
_column_246,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape43 userDiscoveryUserRelations = Shape43(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_user_relations',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
|
||||||
|
columns: [_column_225, _column_226, _column_227],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape44 userDiscoveryOtherPromotions = Shape44(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_other_promotions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(from_contact_id, public_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_226,
|
||||||
|
_column_228,
|
||||||
|
_column_229,
|
||||||
|
_column_230,
|
||||||
|
_column_231,
|
||||||
|
_column_227,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape45 userDiscoveryOwnPromotions = Shape45(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_own_promotions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_232, _column_183, _column_233],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape46 userDiscoveryShares = Shape46(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_shares',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_234, _column_235, _column_175],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape47 shortcuts = Shape47(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'shortcuts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_173, _column_236, _column_237],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape48 shortcutMembers = Shape48(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'shortcut_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shortcut_id, group_id)'],
|
||||||
|
columns: [_column_238, _column_158],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape58 extends i0.VersionedTable {
|
||||||
|
Shape58({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get mediaId =>
|
||||||
|
columnsByName['media_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get uploadState =>
|
||||||
|
columnsByName['upload_state']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get cloudState =>
|
||||||
|
columnsByName['cloud_state']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get blurhash =>
|
||||||
|
columnsByName['blurhash']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get downloadState =>
|
||||||
|
columnsByName['download_state']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get requiresAuthentication =>
|
||||||
|
columnsByName['requires_authentication']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get stored =>
|
||||||
|
columnsByName['stored']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get isDraftMedia =>
|
||||||
|
columnsByName['is_draft_media']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get isFavorite =>
|
||||||
|
columnsByName['is_favorite']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get hasCropAnalyzed =>
|
||||||
|
columnsByName['has_crop_analyzed']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get preProgressingProcess =>
|
||||||
|
columnsByName['pre_progressing_process']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get reuploadRequestedBy =>
|
||||||
|
columnsByName['reupload_requested_by']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get displayLimitInMilliseconds =>
|
||||||
|
columnsByName['display_limit_in_milliseconds']!
|
||||||
|
as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get removeAudio =>
|
||||||
|
columnsByName['remove_audio']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get downloadToken =>
|
||||||
|
columnsByName['download_token']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptionKey =>
|
||||||
|
columnsByName['encryption_key']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptionMac =>
|
||||||
|
columnsByName['encryption_mac']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptionNonce =>
|
||||||
|
columnsByName['encryption_nonce']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get storedFileHash =>
|
||||||
|
columnsByName['stored_file_hash']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<int> get hasThumbnail =>
|
||||||
|
columnsByName['has_thumbnail']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get sizeInBytes =>
|
||||||
|
columnsByName['size_in_bytes']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get createdAtMonth =>
|
||||||
|
columnsByName['created_at_month']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_255(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>(
|
||||||
|
'cloud_state',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
$customConstraints: 'NOT NULL DEFAULT \'none\'',
|
||||||
|
defaultValue: const i1.CustomExpression('\'none\''),
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<String> _column_256(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>(
|
||||||
|
'blurhash',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
$customConstraints: 'NULL',
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
|
@ -11622,6 +12155,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
||||||
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -11730,6 +12264,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from21To22(migrator, schema);
|
await from21To22(migrator, schema);
|
||||||
return 22;
|
return 22;
|
||||||
|
case 22:
|
||||||
|
final schema = Schema23(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from22To23(migrator, schema);
|
||||||
|
return 23;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -11758,6 +12297,7 @@ i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
||||||
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
|
|
@ -11781,5 +12321,6 @@ i1.OnUpgrade stepByStep({
|
||||||
from19To20: from19To20,
|
from19To20: from19To20,
|
||||||
from20To21: from20To21,
|
from20To21: from20To21,
|
||||||
from21To22: from21To22,
|
from21To22: from21To22,
|
||||||
|
from22To23: from22To23,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1265,13 +1265,13 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @proFeature3.
|
/// No description provided for @proFeature3.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'✓ Restore flames'**
|
/// **'✓ 25GB Memories storage'**
|
||||||
String get proFeature3;
|
String get proFeature3;
|
||||||
|
|
||||||
/// No description provided for @proFeature4.
|
/// No description provided for @proFeature4.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'✓ Support twonly'**
|
/// **'✓ Restore flames'**
|
||||||
String get proFeature4;
|
String get proFeature4;
|
||||||
|
|
||||||
/// No description provided for @familyFeature1.
|
/// No description provided for @familyFeature1.
|
||||||
|
|
@ -1289,7 +1289,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @familyFeature3.
|
/// No description provided for @familyFeature3.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'✓ Restore flames'**
|
/// **'✓ 50GB Memories storage'**
|
||||||
String get familyFeature3;
|
String get familyFeature3;
|
||||||
|
|
||||||
/// No description provided for @familyFeature4.
|
/// No description provided for @familyFeature4.
|
||||||
|
|
|
||||||
|
|
@ -652,10 +652,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get proFeature2 => '✓ 1 zusätzlicher Plus Benutzer';
|
String get proFeature2 => '✓ 1 zusätzlicher Plus Benutzer';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature3 => '✓ Flammen wiederherstellen';
|
String get proFeature3 => '✓ 25GB Memories Speicher';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => '✓ twonly unterstützen';
|
String get proFeature4 => '✓ Flammen wiederherstellen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||||
|
|
@ -664,10 +664,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get familyFeature2 => '✓ 4 zusätzliche Plus Benutzer';
|
String get familyFeature2 => '✓ 4 zusätzliche Plus Benutzer';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature3 => '✓ Flammen wiederherstellen';
|
String get familyFeature3 => '50GB Memories Speicher';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature4 => '✓ twonly unterstützen';
|
String get familyFeature4 => '✓ Flammen wiederherstellen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get freeFeature1 => '✓ 10 Medien-Datei-Uploads pro Tag';
|
String get freeFeature1 => '✓ 10 Medien-Datei-Uploads pro Tag';
|
||||||
|
|
|
||||||
|
|
@ -647,10 +647,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get proFeature2 => '✓ 1 additional Plus user';
|
String get proFeature2 => '✓ 1 additional Plus user';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature3 => '✓ Restore flames';
|
String get proFeature3 => '✓ 25GB Memories storage';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => '✓ Support twonly';
|
String get proFeature4 => '✓ Restore flames';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature1 => '✓ Unlimited media file uploads';
|
String get familyFeature1 => '✓ Unlimited media file uploads';
|
||||||
|
|
@ -659,7 +659,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get familyFeature2 => '✓ 4 additional Plus user';
|
String get familyFeature2 => '✓ 4 additional Plus user';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature3 => '✓ Restore flames';
|
String get familyFeature3 => '✓ 50GB Memories storage';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature4 => '✓ Support twonly';
|
String get familyFeature4 => '✓ Support twonly';
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit ef0aed22f39a7453a9dbdbe26a098159b792dbac
|
Subproject commit 499ad4a7b703c1dff414f531c528894ad0c91799
|
||||||
|
|
@ -11,9 +11,6 @@ ReceivedRecoveryShare _$ReceivedRecoveryShareFromJson(
|
||||||
) => ReceivedRecoveryShare(
|
) => ReceivedRecoveryShare(
|
||||||
messageId: (json['messageId'] as num).toInt(),
|
messageId: (json['messageId'] as num).toInt(),
|
||||||
trustedFriendDisplayName: json['trustedFriendDisplayName'] as String,
|
trustedFriendDisplayName: json['trustedFriendDisplayName'] as String,
|
||||||
trustedFriendAvatarSvg: (json['trustedFriendAvatarSvg'] as List<dynamic>?)
|
|
||||||
?.map((e) => (e as num).toInt())
|
|
||||||
.toList(),
|
|
||||||
myDisplayName: json['myDisplayName'] as String,
|
myDisplayName: json['myDisplayName'] as String,
|
||||||
myUserId: (json['myUserId'] as num).toInt(),
|
myUserId: (json['myUserId'] as num).toInt(),
|
||||||
myAvatarSvg: (json['myAvatarSvg'] as List<dynamic>?)
|
myAvatarSvg: (json['myAvatarSvg'] as List<dynamic>?)
|
||||||
|
|
@ -23,6 +20,9 @@ ReceivedRecoveryShare _$ReceivedRecoveryShareFromJson(
|
||||||
sharedSecretDataBytes: (json['sharedSecretDataBytes'] as List<dynamic>)
|
sharedSecretDataBytes: (json['sharedSecretDataBytes'] as List<dynamic>)
|
||||||
.map((e) => (e as num).toInt())
|
.map((e) => (e as num).toInt())
|
||||||
.toList(),
|
.toList(),
|
||||||
|
trustedFriendAvatarSvg: (json['trustedFriendAvatarSvg'] as List<dynamic>?)
|
||||||
|
?.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$ReceivedRecoveryShareToJson(
|
Map<String, dynamic> _$ReceivedRecoveryShareToJson(
|
||||||
|
|
|
||||||
|
|
@ -150,8 +150,8 @@ class UserData {
|
||||||
@Deprecated('Use the secure storage in rust')
|
@Deprecated('Use the secure storage in rust')
|
||||||
TwonlySafeBackup? twonlySafeBackup;
|
TwonlySafeBackup? twonlySafeBackup;
|
||||||
|
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: true)
|
||||||
bool isBackupEnabled = false;
|
bool isBackupEnabled = true;
|
||||||
|
|
||||||
PasswordLessRecovery? passwordLessRecovery;
|
PasswordLessRecovery? passwordLessRecovery;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
: TwonlySafeBackup.fromJson(
|
: TwonlySafeBackup.fromJson(
|
||||||
json['twonlySafeBackup'] as Map<String, dynamic>,
|
json['twonlySafeBackup'] as Map<String, dynamic>,
|
||||||
)
|
)
|
||||||
..isBackupEnabled = json['isBackupEnabled'] as bool? ?? false
|
..isBackupEnabled = json['isBackupEnabled'] as bool? ?? true
|
||||||
..passwordLessRecovery = json['passwordLessRecovery'] == null
|
..passwordLessRecovery = json['passwordLessRecovery'] == null
|
||||||
? null
|
? null
|
||||||
: PasswordLessRecovery.fromJson(
|
: PasswordLessRecovery.fromJson(
|
||||||
|
|
|
||||||
|
|
@ -2671,10 +2671,12 @@ class ApplicationData_RequestMemoriesUpload extends $pb.GeneratedMessage {
|
||||||
factory ApplicationData_RequestMemoriesUpload({
|
factory ApplicationData_RequestMemoriesUpload({
|
||||||
$fixnum.Int64? size,
|
$fixnum.Int64? size,
|
||||||
$fixnum.Int64? originalDate,
|
$fixnum.Int64? originalDate,
|
||||||
|
$core.String? mediaId,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (size != null) result.size = size;
|
if (size != null) result.size = size;
|
||||||
if (originalDate != null) result.originalDate = originalDate;
|
if (originalDate != null) result.originalDate = originalDate;
|
||||||
|
if (mediaId != null) result.mediaId = mediaId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2695,6 +2697,7 @@ class ApplicationData_RequestMemoriesUpload extends $pb.GeneratedMessage {
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..aInt64(1, _omitFieldNames ? '' : 'size')
|
..aInt64(1, _omitFieldNames ? '' : 'size')
|
||||||
..aInt64(2, _omitFieldNames ? '' : 'originalDate')
|
..aInt64(2, _omitFieldNames ? '' : 'originalDate')
|
||||||
|
..aOS(3, _omitFieldNames ? '' : 'mediaId')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -2737,6 +2740,15 @@ class ApplicationData_RequestMemoriesUpload extends $pb.GeneratedMessage {
|
||||||
$core.bool hasOriginalDate() => $_has(1);
|
$core.bool hasOriginalDate() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearOriginalDate() => $_clearField(2);
|
void clearOriginalDate() => $_clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.String get mediaId => $_getSZ(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set mediaId($core.String value) => $_setString(2, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasMediaId() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearMediaId() => $_clearField(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApplicationData_ConfirmMemoriesUpload extends $pb.GeneratedMessage {
|
class ApplicationData_ConfirmMemoriesUpload extends $pb.GeneratedMessage {
|
||||||
|
|
@ -2873,9 +2885,11 @@ class ApplicationData_GetMemoriesList extends $pb.GeneratedMessage {
|
||||||
class ApplicationData_GetMemoriesUrl extends $pb.GeneratedMessage {
|
class ApplicationData_GetMemoriesUrl extends $pb.GeneratedMessage {
|
||||||
factory ApplicationData_GetMemoriesUrl({
|
factory ApplicationData_GetMemoriesUrl({
|
||||||
$core.String? mediaId,
|
$core.String? mediaId,
|
||||||
|
$core.bool? thumbnail,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (mediaId != null) result.mediaId = mediaId;
|
if (mediaId != null) result.mediaId = mediaId;
|
||||||
|
if (thumbnail != null) result.thumbnail = thumbnail;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2894,6 +2908,7 @@ class ApplicationData_GetMemoriesUrl extends $pb.GeneratedMessage {
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..aOS(1, _omitFieldNames ? '' : 'mediaId')
|
..aOS(1, _omitFieldNames ? '' : 'mediaId')
|
||||||
|
..aOB(2, _omitFieldNames ? '' : 'thumbnail')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -2926,6 +2941,15 @@ class ApplicationData_GetMemoriesUrl extends $pb.GeneratedMessage {
|
||||||
$core.bool hasMediaId() => $_has(0);
|
$core.bool hasMediaId() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearMediaId() => $_clearField(1);
|
void clearMediaId() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool get thumbnail => $_getBF(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set thumbnail($core.bool value) => $_setBool(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasThumbnail() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearThumbnail() => $_clearField(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApplicationData_GetMemoriesUsage extends $pb.GeneratedMessage {
|
class ApplicationData_GetMemoriesUsage extends $pb.GeneratedMessage {
|
||||||
|
|
@ -2972,6 +2996,64 @@ class ApplicationData_GetMemoriesUsage extends $pb.GeneratedMessage {
|
||||||
static ApplicationData_GetMemoriesUsage? _defaultInstance;
|
static ApplicationData_GetMemoriesUsage? _defaultInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ApplicationData_DeleteMemory extends $pb.GeneratedMessage {
|
||||||
|
factory ApplicationData_DeleteMemory({
|
||||||
|
$core.String? mediaId,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (mediaId != null) result.mediaId = mediaId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationData_DeleteMemory._();
|
||||||
|
|
||||||
|
factory ApplicationData_DeleteMemory.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory ApplicationData_DeleteMemory.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'ApplicationData.DeleteMemory',
|
||||||
|
package:
|
||||||
|
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOS(1, _omitFieldNames ? '' : 'mediaId')
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ApplicationData_DeleteMemory clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ApplicationData_DeleteMemory copyWith(
|
||||||
|
void Function(ApplicationData_DeleteMemory) updates) =>
|
||||||
|
super.copyWith(
|
||||||
|
(message) => updates(message as ApplicationData_DeleteMemory))
|
||||||
|
as ApplicationData_DeleteMemory;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ApplicationData_DeleteMemory create() =>
|
||||||
|
ApplicationData_DeleteMemory._();
|
||||||
|
@$core.override
|
||||||
|
ApplicationData_DeleteMemory createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ApplicationData_DeleteMemory getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<ApplicationData_DeleteMemory>(create);
|
||||||
|
static ApplicationData_DeleteMemory? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.String get mediaId => $_getSZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set mediaId($core.String value) => $_setString(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasMediaId() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearMediaId() => $_clearField(1);
|
||||||
|
}
|
||||||
|
|
||||||
enum ApplicationData_ApplicationData {
|
enum ApplicationData_ApplicationData {
|
||||||
textMessage,
|
textMessage,
|
||||||
getUserByUsername,
|
getUserByUsername,
|
||||||
|
|
@ -3006,6 +3088,7 @@ enum ApplicationData_ApplicationData {
|
||||||
getMemoriesList,
|
getMemoriesList,
|
||||||
getMemoriesUrl,
|
getMemoriesUrl,
|
||||||
getMemoriesUsage,
|
getMemoriesUsage,
|
||||||
|
deleteMemory,
|
||||||
notSet
|
notSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3044,6 +3127,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
ApplicationData_GetMemoriesList? getMemoriesList,
|
ApplicationData_GetMemoriesList? getMemoriesList,
|
||||||
ApplicationData_GetMemoriesUrl? getMemoriesUrl,
|
ApplicationData_GetMemoriesUrl? getMemoriesUrl,
|
||||||
ApplicationData_GetMemoriesUsage? getMemoriesUsage,
|
ApplicationData_GetMemoriesUsage? getMemoriesUsage,
|
||||||
|
ApplicationData_DeleteMemory? deleteMemory,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (textMessage != null) result.textMessage = textMessage;
|
if (textMessage != null) result.textMessage = textMessage;
|
||||||
|
|
@ -3089,6 +3173,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
if (getMemoriesList != null) result.getMemoriesList = getMemoriesList;
|
if (getMemoriesList != null) result.getMemoriesList = getMemoriesList;
|
||||||
if (getMemoriesUrl != null) result.getMemoriesUrl = getMemoriesUrl;
|
if (getMemoriesUrl != null) result.getMemoriesUrl = getMemoriesUrl;
|
||||||
if (getMemoriesUsage != null) result.getMemoriesUsage = getMemoriesUsage;
|
if (getMemoriesUsage != null) result.getMemoriesUsage = getMemoriesUsage;
|
||||||
|
if (deleteMemory != null) result.deleteMemory = deleteMemory;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3136,6 +3221,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
35: ApplicationData_ApplicationData.getMemoriesList,
|
35: ApplicationData_ApplicationData.getMemoriesList,
|
||||||
36: ApplicationData_ApplicationData.getMemoriesUrl,
|
36: ApplicationData_ApplicationData.getMemoriesUrl,
|
||||||
37: ApplicationData_ApplicationData.getMemoriesUsage,
|
37: ApplicationData_ApplicationData.getMemoriesUsage,
|
||||||
|
38: ApplicationData_ApplicationData.deleteMemory,
|
||||||
0: ApplicationData_ApplicationData.notSet
|
0: ApplicationData_ApplicationData.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
|
@ -3176,7 +3262,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
34,
|
34,
|
||||||
35,
|
35,
|
||||||
36,
|
36,
|
||||||
37
|
37,
|
||||||
|
38
|
||||||
])
|
])
|
||||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
||||||
protoName: 'textMessage',
|
protoName: 'textMessage',
|
||||||
|
|
@ -3289,6 +3376,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
..aOM<ApplicationData_GetMemoriesUsage>(
|
..aOM<ApplicationData_GetMemoriesUsage>(
|
||||||
37, _omitFieldNames ? '' : 'getMemoriesUsage',
|
37, _omitFieldNames ? '' : 'getMemoriesUsage',
|
||||||
subBuilder: ApplicationData_GetMemoriesUsage.create)
|
subBuilder: ApplicationData_GetMemoriesUsage.create)
|
||||||
|
..aOM<ApplicationData_DeleteMemory>(
|
||||||
|
38, _omitFieldNames ? '' : 'deleteMemory',
|
||||||
|
subBuilder: ApplicationData_DeleteMemory.create)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -3343,6 +3433,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
@$pb.TagNumber(35)
|
@$pb.TagNumber(35)
|
||||||
@$pb.TagNumber(36)
|
@$pb.TagNumber(36)
|
||||||
@$pb.TagNumber(37)
|
@$pb.TagNumber(37)
|
||||||
|
@$pb.TagNumber(38)
|
||||||
ApplicationData_ApplicationData whichApplicationData() =>
|
ApplicationData_ApplicationData whichApplicationData() =>
|
||||||
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
|
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
|
|
@ -3378,6 +3469,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
@$pb.TagNumber(35)
|
@$pb.TagNumber(35)
|
||||||
@$pb.TagNumber(36)
|
@$pb.TagNumber(36)
|
||||||
@$pb.TagNumber(37)
|
@$pb.TagNumber(37)
|
||||||
|
@$pb.TagNumber(38)
|
||||||
void clearApplicationData() => $_clearField($_whichOneof(0));
|
void clearApplicationData() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
|
|
@ -3775,6 +3867,17 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
void clearGetMemoriesUsage() => $_clearField(37);
|
void clearGetMemoriesUsage() => $_clearField(37);
|
||||||
@$pb.TagNumber(37)
|
@$pb.TagNumber(37)
|
||||||
ApplicationData_GetMemoriesUsage ensureGetMemoriesUsage() => $_ensure(32);
|
ApplicationData_GetMemoriesUsage ensureGetMemoriesUsage() => $_ensure(32);
|
||||||
|
|
||||||
|
@$pb.TagNumber(38)
|
||||||
|
ApplicationData_DeleteMemory get deleteMemory => $_getN(33);
|
||||||
|
@$pb.TagNumber(38)
|
||||||
|
set deleteMemory(ApplicationData_DeleteMemory value) => $_setField(38, value);
|
||||||
|
@$pb.TagNumber(38)
|
||||||
|
$core.bool hasDeleteMemory() => $_has(33);
|
||||||
|
@$pb.TagNumber(38)
|
||||||
|
void clearDeleteMemory() => $_clearField(38);
|
||||||
|
@$pb.TagNumber(38)
|
||||||
|
ApplicationData_DeleteMemory ensureDeleteMemory() => $_ensure(33);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Response_PreKey extends $pb.GeneratedMessage {
|
class Response_PreKey extends $pb.GeneratedMessage {
|
||||||
|
|
|
||||||
|
|
@ -778,6 +778,15 @@ const ApplicationData$json = {
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'getMemoriesUsage'
|
'10': 'getMemoriesUsage'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'delete_memory',
|
||||||
|
'3': 38,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.client_to_server.ApplicationData.DeleteMemory',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'deleteMemory'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'3': [
|
'3': [
|
||||||
ApplicationData_TextMessage$json,
|
ApplicationData_TextMessage$json,
|
||||||
|
|
@ -805,7 +814,8 @@ const ApplicationData$json = {
|
||||||
ApplicationData_ConfirmMemoriesUpload$json,
|
ApplicationData_ConfirmMemoriesUpload$json,
|
||||||
ApplicationData_GetMemoriesList$json,
|
ApplicationData_GetMemoriesList$json,
|
||||||
ApplicationData_GetMemoriesUrl$json,
|
ApplicationData_GetMemoriesUrl$json,
|
||||||
ApplicationData_GetMemoriesUsage$json
|
ApplicationData_GetMemoriesUsage$json,
|
||||||
|
ApplicationData_DeleteMemory$json
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': 'ApplicationData'},
|
{'1': 'ApplicationData'},
|
||||||
|
|
@ -1026,6 +1036,7 @@ const ApplicationData_RequestMemoriesUpload$json = {
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'size', '3': 1, '4': 1, '5': 3, '10': 'size'},
|
{'1': 'size', '3': 1, '4': 1, '5': 3, '10': 'size'},
|
||||||
{'1': 'original_date', '3': 2, '4': 1, '5': 3, '10': 'originalDate'},
|
{'1': 'original_date', '3': 2, '4': 1, '5': 3, '10': 'originalDate'},
|
||||||
|
{'1': 'media_id', '3': 3, '4': 1, '5': 9, '10': 'mediaId'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1051,6 +1062,7 @@ const ApplicationData_GetMemoriesUrl$json = {
|
||||||
'1': 'GetMemoriesUrl',
|
'1': 'GetMemoriesUrl',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||||
|
{'1': 'thumbnail', '3': 2, '4': 1, '5': 8, '10': 'thumbnail'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1059,6 +1071,14 @@ const ApplicationData_GetMemoriesUsage$json = {
|
||||||
'1': 'GetMemoriesUsage',
|
'1': 'GetMemoriesUsage',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||||
|
const ApplicationData_DeleteMemory$json = {
|
||||||
|
'1': 'DeleteMemory',
|
||||||
|
'2': [
|
||||||
|
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||||
|
|
@ -1118,36 +1138,39 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||||
'AFIPZ2V0TWVtb3JpZXNMaXN0ElwKEGdldF9tZW1vcmllc191cmwYJCABKAsyMC5jbGllbnRfdG'
|
'AFIPZ2V0TWVtb3JpZXNMaXN0ElwKEGdldF9tZW1vcmllc191cmwYJCABKAsyMC5jbGllbnRfdG'
|
||||||
'9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRNZW1vcmllc1VybEgAUg5nZXRNZW1vcmllc1Vy'
|
'9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRNZW1vcmllc1VybEgAUg5nZXRNZW1vcmllc1Vy'
|
||||||
'bBJiChJnZXRfbWVtb3JpZXNfdXNhZ2UYJSABKAsyMi5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2'
|
'bBJiChJnZXRfbWVtb3JpZXNfdXNhZ2UYJSABKAsyMi5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2'
|
||||||
'F0aW9uRGF0YS5HZXRNZW1vcmllc1VzYWdlSABSEGdldE1lbW9yaWVzVXNhZ2UaagoLVGV4dE1l'
|
'F0aW9uRGF0YS5HZXRNZW1vcmllc1VzYWdlSABSEGdldE1lbW9yaWVzVXNhZ2USVQoNZGVsZXRl'
|
||||||
'c3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAyABKAxSBGJvZHkSIAoJcH'
|
'X21lbW9yeRgmIAEoCzIuLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkRlbGV0ZU'
|
||||||
'VzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdXNoX2RhdGEaLwoRR2V0VXNlckJ5'
|
'1lbW9yeUgAUgxkZWxldGVNZW1vcnkaagoLVGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IG'
|
||||||
'VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGiwKDkNoYW5nZVVzZXJuYW1lEh'
|
'dXNlcklkEhIKBGJvZHkYAyABKAxSBGJvZHkSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2hEYX'
|
||||||
'oKCHVzZXJuYW1lGAEgASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpn'
|
'RhiAEBQgwKCl9wdXNoX2RhdGEaLwoRR2V0VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASAB'
|
||||||
'b29nbGVfZmNtGAEgASgJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIA'
|
'KAlSCHVzZXJuYW1lGiwKDkNoYW5nZVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2Vybm'
|
||||||
'EoA1IGdXNlcklkGhMKEUdldEF2YWlsYWJsZVBsYW5zGhUKE0dldEN1cnJlbnRQbGFuSW5mb3Ma'
|
'FtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tlbhIdCgpnb29nbGVfZmNtGAEgASgJUglnb29nbGVG'
|
||||||
'LwoUUmVtb3ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldF'
|
'Y20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGhMKEUdldEF2YWlsYW'
|
||||||
'ByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJl'
|
'JsZVBsYW5zGhUKE0dldEN1cnJlbnRQbGFuSW5mb3MaLwoUUmVtb3ZlQWRkaXRpb25hbFVzZXIS'
|
||||||
'S2V5QnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcm'
|
'FwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2'
|
||||||
'VLZXkSKAoQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVk'
|
'lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9pZBgB'
|
||||||
'X3ByZWtleRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGA'
|
'IAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV9pZB'
|
||||||
'MgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2Fk'
|
'gBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVkUHJl'
|
||||||
'X3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdX'
|
'a2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaWduYX'
|
||||||
'Nlcl9pZBgBIAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoL'
|
'R1cmUaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRv'
|
||||||
'SVBBUHVyY2hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIA'
|
'a2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRVc2'
|
||||||
'EoCVIGc291cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRh'
|
'VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF9p'
|
||||||
'Gg8KDUlQQUZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudBosChFBZGRBZGRpdGlvbmFsVXNlch'
|
'ZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNhdG'
|
||||||
'IXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMAoNU2V0TG9naW5Ub2tlbhIfCgtsb2dpbl90b2tl'
|
'lvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRGVs'
|
||||||
'bhgBIAEoDFIKbG9naW5Ub2tlbhoMCgpEZXByZWNhdGVkGo4BChxSZWdpc3RlclBhc3N3b3JkTG'
|
'ZXRlQWNjb3VudBosChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySW'
|
||||||
'Vzc1JlY292ZXJ5Ei4KEmVuY3J5cHRlZFNlcnZlcktleRgBIAEoDFISZW5jcnlwdGVkU2VydmVy'
|
'QaMAoNU2V0TG9naW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhoMCgpE'
|
||||||
'S2V5EisKDnBpblVubG9ja1Rva2VuGAIgASgMSABSDnBpblVubG9ja1Rva2VuiAEBQhEKD19waW'
|
'ZXByZWNhdGVkGo4BChxSZWdpc3RlclBhc3N3b3JkTGVzc1JlY292ZXJ5Ei4KEmVuY3J5cHRlZF'
|
||||||
'5VbmxvY2tUb2tlbhpwChhQYXNzd29yZGxlc3NOb3RpZmljYXRpb24SJwoPbm90aWZpY2F0aW9u'
|
'NlcnZlcktleRgBIAEoDFISZW5jcnlwdGVkU2VydmVyS2V5EisKDnBpblVubG9ja1Rva2VuGAIg'
|
||||||
'X2lkGAEgASgJUg5ub3RpZmljYXRpb25JZBIrChFlbmNyeXB0ZWRfbWVzc2FnZRgCIAEoDFIQZW'
|
'ASgMSABSDnBpblVubG9ja1Rva2VuiAEBQhEKD19waW5VbmxvY2tUb2tlbhpwChhQYXNzd29yZG'
|
||||||
'5jcnlwdGVkTWVzc2FnZRpQChVSZXF1ZXN0TWVtb3JpZXNVcGxvYWQSEgoEc2l6ZRgBIAEoA1IE'
|
'xlc3NOb3RpZmljYXRpb24SJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3RpZmljYXRpb25J'
|
||||||
'c2l6ZRIjCg1vcmlnaW5hbF9kYXRlGAIgASgDUgxvcmlnaW5hbERhdGUaMgoVQ29uZmlybU1lbW'
|
'ZBIrChFlbmNyeXB0ZWRfbWVzc2FnZRgCIAEoDFIQZW5jcnlwdGVkTWVzc2FnZRprChVSZXF1ZX'
|
||||||
'9yaWVzVXBsb2FkEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkGkgKD0dldE1lbW9yaWVzTGlz'
|
'N0TWVtb3JpZXNVcGxvYWQSEgoEc2l6ZRgBIAEoA1IEc2l6ZRIjCg1vcmlnaW5hbF9kYXRlGAIg'
|
||||||
'dBIfCgtvZmZzZXRfZGF0ZRgBIAEoA1IKb2Zmc2V0RGF0ZRIUCgVsaW1pdBgCIAEoA1IFbGltaX'
|
'ASgDUgxvcmlnaW5hbERhdGUSGQoIbWVkaWFfaWQYAyABKAlSB21lZGlhSWQaMgoVQ29uZmlybU'
|
||||||
'QaKwoOR2V0TWVtb3JpZXNVcmwSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQaEgoQR2V0TWVt'
|
'1lbW9yaWVzVXBsb2FkEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkGkgKD0dldE1lbW9yaWVz'
|
||||||
'b3JpZXNVc2FnZUIRCg9BcHBsaWNhdGlvbkRhdGE=');
|
'TGlzdBIfCgtvZmZzZXRfZGF0ZRgBIAEoA1IKb2Zmc2V0RGF0ZRIUCgVsaW1pdBgCIAEoA1IFbG'
|
||||||
|
'ltaXQaSQoOR2V0TWVtb3JpZXNVcmwSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQSHAoJdGh1'
|
||||||
|
'bWJuYWlsGAIgASgIUgl0aHVtYm5haWwaEgoQR2V0TWVtb3JpZXNVc2FnZRopCgxEZWxldGVNZW'
|
||||||
|
'1vcnkSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWRCEQoPQXBwbGljYXRpb25EYXRh');
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
const Response$json = {
|
const Response$json = {
|
||||||
|
|
|
||||||
|
|
@ -1647,17 +1647,82 @@ class Response_PasswordlessNotificationMessages extends $pb.GeneratedMessage {
|
||||||
$_getList(0);
|
$_getList(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Response_PresignedPost extends $pb.GeneratedMessage {
|
||||||
|
factory Response_PresignedPost({
|
||||||
|
$core.String? url,
|
||||||
|
$core.Iterable<$core.MapEntry<$core.String, $core.String>>? fields,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (url != null) result.url = url;
|
||||||
|
if (fields != null) result.fields.addEntries(fields);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response_PresignedPost._();
|
||||||
|
|
||||||
|
factory Response_PresignedPost.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory Response_PresignedPost.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'Response.PresignedPost',
|
||||||
|
package:
|
||||||
|
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOS(1, _omitFieldNames ? '' : 'url')
|
||||||
|
..m<$core.String, $core.String>(2, _omitFieldNames ? '' : 'fields',
|
||||||
|
entryClassName: 'Response.PresignedPost.FieldsEntry',
|
||||||
|
keyFieldType: $pb.PbFieldType.OS,
|
||||||
|
valueFieldType: $pb.PbFieldType.OS,
|
||||||
|
packageName: const $pb.PackageName('server_to_client'))
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
Response_PresignedPost clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
Response_PresignedPost copyWith(
|
||||||
|
void Function(Response_PresignedPost) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as Response_PresignedPost))
|
||||||
|
as Response_PresignedPost;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static Response_PresignedPost create() => Response_PresignedPost._();
|
||||||
|
@$core.override
|
||||||
|
Response_PresignedPost createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static Response_PresignedPost getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<Response_PresignedPost>(create);
|
||||||
|
static Response_PresignedPost? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.String get url => $_getSZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set url($core.String value) => $_setString(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasUrl() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearUrl() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$pb.PbMap<$core.String, $core.String> get fields => $_getMap(1);
|
||||||
|
}
|
||||||
|
|
||||||
class Response_MemoriesUploadUrls extends $pb.GeneratedMessage {
|
class Response_MemoriesUploadUrls extends $pb.GeneratedMessage {
|
||||||
factory Response_MemoriesUploadUrls({
|
factory Response_MemoriesUploadUrls({
|
||||||
$core.String? mediaId,
|
$core.String? mediaId,
|
||||||
$core.String? thumbnailUploadUrl,
|
Response_PresignedPost? thumbnailUpload,
|
||||||
$core.String? fullUploadUrl,
|
Response_PresignedPost? fullUpload,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (mediaId != null) result.mediaId = mediaId;
|
if (mediaId != null) result.mediaId = mediaId;
|
||||||
if (thumbnailUploadUrl != null)
|
if (thumbnailUpload != null) result.thumbnailUpload = thumbnailUpload;
|
||||||
result.thumbnailUploadUrl = thumbnailUploadUrl;
|
if (fullUpload != null) result.fullUpload = fullUpload;
|
||||||
if (fullUploadUrl != null) result.fullUploadUrl = fullUploadUrl;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1676,8 +1741,10 @@ class Response_MemoriesUploadUrls extends $pb.GeneratedMessage {
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..aOS(1, _omitFieldNames ? '' : 'mediaId')
|
..aOS(1, _omitFieldNames ? '' : 'mediaId')
|
||||||
..aOS(2, _omitFieldNames ? '' : 'thumbnailUploadUrl')
|
..aOM<Response_PresignedPost>(2, _omitFieldNames ? '' : 'thumbnailUpload',
|
||||||
..aOS(3, _omitFieldNames ? '' : 'fullUploadUrl')
|
subBuilder: Response_PresignedPost.create)
|
||||||
|
..aOM<Response_PresignedPost>(3, _omitFieldNames ? '' : 'fullUpload',
|
||||||
|
subBuilder: Response_PresignedPost.create)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -1712,22 +1779,26 @@ class Response_MemoriesUploadUrls extends $pb.GeneratedMessage {
|
||||||
void clearMediaId() => $_clearField(1);
|
void clearMediaId() => $_clearField(1);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.String get thumbnailUploadUrl => $_getSZ(1);
|
Response_PresignedPost get thumbnailUpload => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set thumbnailUploadUrl($core.String value) => $_setString(1, value);
|
set thumbnailUpload(Response_PresignedPost value) => $_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasThumbnailUploadUrl() => $_has(1);
|
$core.bool hasThumbnailUpload() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearThumbnailUploadUrl() => $_clearField(2);
|
void clearThumbnailUpload() => $_clearField(2);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
Response_PresignedPost ensureThumbnailUpload() => $_ensure(1);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$core.String get fullUploadUrl => $_getSZ(2);
|
Response_PresignedPost get fullUpload => $_getN(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
set fullUploadUrl($core.String value) => $_setString(2, value);
|
set fullUpload(Response_PresignedPost value) => $_setField(3, value);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$core.bool hasFullUploadUrl() => $_has(2);
|
$core.bool hasFullUpload() => $_has(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
void clearFullUploadUrl() => $_clearField(3);
|
void clearFullUpload() => $_clearField(3);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
Response_PresignedPost ensureFullUpload() => $_ensure(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Response_MediaItem extends $pb.GeneratedMessage {
|
class Response_MediaItem extends $pb.GeneratedMessage {
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,7 @@ const Response$json = {
|
||||||
Response_ProofOfWork$json,
|
Response_ProofOfWork$json,
|
||||||
Response_PasswordlessNotificationMessage$json,
|
Response_PasswordlessNotificationMessage$json,
|
||||||
Response_PasswordlessNotificationMessages$json,
|
Response_PasswordlessNotificationMessages$json,
|
||||||
|
Response_PresignedPost$json,
|
||||||
Response_MemoriesUploadUrls$json,
|
Response_MemoriesUploadUrls$json,
|
||||||
Response_MediaItem$json,
|
Response_MediaItem$json,
|
||||||
Response_MemoriesList$json,
|
Response_MemoriesList$json,
|
||||||
|
|
@ -553,19 +554,54 @@ const Response_PasswordlessNotificationMessages$json = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
|
const Response_PresignedPost$json = {
|
||||||
|
'1': 'PresignedPost',
|
||||||
|
'2': [
|
||||||
|
{'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'},
|
||||||
|
{
|
||||||
|
'1': 'fields',
|
||||||
|
'3': 2,
|
||||||
|
'4': 3,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.server_to_client.Response.PresignedPost.FieldsEntry',
|
||||||
|
'10': 'fields'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'3': [Response_PresignedPost_FieldsEntry$json],
|
||||||
|
};
|
||||||
|
|
||||||
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
|
const Response_PresignedPost_FieldsEntry$json = {
|
||||||
|
'1': 'FieldsEntry',
|
||||||
|
'2': [
|
||||||
|
{'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
|
||||||
|
{'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
|
||||||
|
],
|
||||||
|
'7': {'7': true},
|
||||||
|
};
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
const Response_MemoriesUploadUrls$json = {
|
const Response_MemoriesUploadUrls$json = {
|
||||||
'1': 'MemoriesUploadUrls',
|
'1': 'MemoriesUploadUrls',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||||
{
|
{
|
||||||
'1': 'thumbnail_upload_url',
|
'1': 'thumbnail_upload',
|
||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 9,
|
'5': 11,
|
||||||
'10': 'thumbnailUploadUrl'
|
'6': '.server_to_client.Response.PresignedPost',
|
||||||
|
'10': 'thumbnailUpload'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'full_upload',
|
||||||
|
'3': 3,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.server_to_client.Response.PresignedPost',
|
||||||
|
'10': 'fullUpload'
|
||||||
},
|
},
|
||||||
{'1': 'full_upload_url', '3': 3, '4': 1, '5': 9, '10': 'fullUploadUrl'},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -843,42 +879,46 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
||||||
'Bhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2USDgoCaWQYASABKANSAmlkEisKEWVuY3J5'
|
'Bhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2USDgoCaWQYASABKANSAmlkEisKEWVuY3J5'
|
||||||
'cHRlZF9tZXNzYWdlGAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdlGnoKIFBhc3N3b3JkbGVzc05vdG'
|
'cHRlZF9tZXNzYWdlGAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdlGnoKIFBhc3N3b3JkbGVzc05vdG'
|
||||||
'lmaWNhdGlvbk1lc3NhZ2VzElYKCG1lc3NhZ2VzGAEgAygLMjouc2VydmVyX3RvX2NsaWVudC5S'
|
'lmaWNhdGlvbk1lc3NhZ2VzElYKCG1lc3NhZ2VzGAEgAygLMjouc2VydmVyX3RvX2NsaWVudC5S'
|
||||||
'ZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlUghtZXNzYWdlcxqJAQoSTW'
|
'ZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlUghtZXNzYWdlcxqqAQoNUH'
|
||||||
'Vtb3JpZXNVcGxvYWRVcmxzEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkEjAKFHRodW1ibmFp'
|
'Jlc2lnbmVkUG9zdBIQCgN1cmwYASABKAlSA3VybBJMCgZmaWVsZHMYAiADKAsyNC5zZXJ2ZXJf'
|
||||||
'bF91cGxvYWRfdXJsGAIgASgJUhJ0aHVtYm5haWxVcGxvYWRVcmwSJgoPZnVsbF91cGxvYWRfdX'
|
'dG9fY2xpZW50LlJlc3BvbnNlLlByZXNpZ25lZFBvc3QuRmllbGRzRW50cnlSBmZpZWxkcxo5Cg'
|
||||||
'JsGAMgASgJUg1mdWxsVXBsb2FkVXJsGoEBCglNZWRpYUl0ZW0SGQoIbWVkaWFfaWQYASABKAlS'
|
'tGaWVsZHNFbnRyeRIQCgNrZXkYASABKAlSA2tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWU6AjgB'
|
||||||
'B21lZGlhSWQSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3JpZ2luYWxEYXRlEjQKFnRodW1ibm'
|
'Gs8BChJNZW1vcmllc1VwbG9hZFVybHMSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQSUwoQdG'
|
||||||
'FpbF9kb3dubG9hZF91cmwYAyABKAlSFHRodW1ibmFpbERvd25sb2FkVXJsGkoKDE1lbW9yaWVz'
|
'h1bWJuYWlsX3VwbG9hZBgCIAEoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJlc2ln'
|
||||||
'TGlzdBI6CgVpdGVtcxgBIAMoCzIkLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuTWVkaWFJdG'
|
'bmVkUG9zdFIPdGh1bWJuYWlsVXBsb2FkEkkKC2Z1bGxfdXBsb2FkGAMgASgLMiguc2VydmVyX3'
|
||||||
'VtUgVpdGVtcxo5CgtNZW1vcmllc1VybBIqChFmdWxsX2Rvd25sb2FkX3VybBgBIAEoCVIPZnVs'
|
'RvX2NsaWVudC5SZXNwb25zZS5QcmVzaWduZWRQb3N0UgpmdWxsVXBsb2FkGoEBCglNZWRpYUl0'
|
||||||
'bERvd25sb2FkVXJsGmcKDU1lbW9yaWVzVXNhZ2USGwoJbWF4X2J5dGVzGAEgASgDUghtYXhCeX'
|
'ZW0SGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3'
|
||||||
'RlcxIjCg1jdXJyZW50X2J5dGVzGAIgASgDUgxjdXJyZW50Qnl0ZXMSFAoFY291bnQYAyABKANS'
|
'JpZ2luYWxEYXRlEjQKFnRodW1ibmFpbF9kb3dubG9hZF91cmwYAyABKAlSFHRodW1ibmFpbERv'
|
||||||
'BWNvdW50GoMMCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSGAoGdXNlcmlkGAIgASgDSABSBn'
|
'd25sb2FkVXJsGkoKDE1lbW9yaWVzTGlzdBI6CgVpdGVtcxgBIAMoCzIkLnNlcnZlcl90b19jbG'
|
||||||
'VzZXJpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1dGhjaGFsbGVuZ2USSgoLdXBsb2Fk'
|
'llbnQuUmVzcG9uc2UuTWVkaWFJdGVtUgVpdGVtcxo5CgtNZW1vcmllc1VybBIqChFmdWxsX2Rv'
|
||||||
'dG9rZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVwbG9hZFRva2VuSABSC3'
|
'd25sb2FkX3VybBgBIAEoCVIPZnVsbERvd25sb2FkVXJsGmcKDU1lbW9yaWVzVXNhZ2USGwoJbW'
|
||||||
'VwbG9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25z'
|
'F4X2J5dGVzGAEgASgDUghtYXhCeXRlcxIjCg1jdXJyZW50X2J5dGVzGAIgASgDUgxjdXJyZW50'
|
||||||
'ZS5Vc2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW4YBiABKAxIAFIJYXV0aHRva2VuEk'
|
'Qnl0ZXMSFAoFY291bnQYAyABKANSBWNvdW50GoMMCgJPaxIUCgROb25lGAEgASgISABSBE5vbm'
|
||||||
'oKDGRlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuRGVwcmVj'
|
'USGAoGdXNlcmlkGAIgASgDSABSBnVzZXJpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1'
|
||||||
'YXRlZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2'
|
'dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9rZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3'
|
||||||
'NsaWVudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMY'
|
'BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2Vy'
|
||||||
'CSABKAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW'
|
'dmVyX3RvX2NsaWVudC5SZXNwb25zZS5Vc2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW'
|
||||||
'5iYWxsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNl'
|
'4YBiABKAxIAFIJYXV0aHRva2VuEkoKDGRlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19j'
|
||||||
'SABSDHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGAsgASgLMiUuc2VydmVyX3RvX2NsaW'
|
'bGllbnQuUmVzcG9uc2UuRGVwcmVjYXRlZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdG'
|
||||||
'VudC5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0ZWQxMRJfChJhZGRhY2NvdW50c2lu'
|
'VkGAggASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1'
|
||||||
'dml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aX'
|
'dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSABKAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLl'
|
||||||
'Rlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2'
|
'BsYW5zSABSBXBsYW5zEk0KDHBsYW5iYWxsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQu'
|
||||||
'ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk'
|
'UmVzcG9uc2UuUGxhbkJhbGxhbmNlSABSDHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGA'
|
||||||
'0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVk'
|
'sgASgLMiUuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0'
|
||||||
'UHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b1'
|
'ZWQxMRJfChJhZGRhY2NvdW50c2ludml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3'
|
||||||
'9jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmsSSQogcGFzc3dvcmRs'
|
'BvbnNlLkFkZEFjY291bnRzSW52aXRlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxv'
|
||||||
'ZXNzX3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcGFzc3dvcmRsZXNzUmVjb3ZlcnlTZX'
|
'YWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW'
|
||||||
'J2ZXJLZXkSiwEKInBhc3N3b3JkbGVzc19ub3RpZmljYXRpb25fbWVzc2FnZXMYESABKAsyOy5z'
|
'5zSABSDmRvd25sb2FkdG9rZW5zEk0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19j'
|
||||||
'ZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2'
|
'bGllbnQuUmVzcG9uc2UuU2lnbmVkUHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV2'
|
||||||
'VzSABSIHBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2VzEmEKFG1lbW9yaWVzX3VwbG9h'
|
'9yaxgPIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJv'
|
||||||
'ZF91cmxzGBIgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5NZW1vcmllc1VwbG9hZF'
|
'b2ZPZldvcmsSSQogcGFzc3dvcmRsZXNzX3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcG'
|
||||||
'VybHNIAFISbWVtb3JpZXNVcGxvYWRVcmxzEk4KDW1lbW9yaWVzX2xpc3QYEyABKAsyJy5zZXJ2'
|
'Fzc3dvcmRsZXNzUmVjb3ZlcnlTZXJ2ZXJLZXkSiwEKInBhc3N3b3JkbGVzc19ub3RpZmljYXRp'
|
||||||
'ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzTGlzdEgAUgxtZW1vcmllc0xpc3QSSwoMbW'
|
'b25fbWVzc2FnZXMYESABKAsyOy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBhc3N3b3JkbG'
|
||||||
'Vtb3JpZXNfdXJsGBQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5NZW1vcmllc1Vy'
|
'Vzc05vdGlmaWNhdGlvbk1lc3NhZ2VzSABSIHBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3Nh'
|
||||||
'bEgAUgttZW1vcmllc1VybBJRCg5tZW1vcmllc191c2FnZRgVIAEoCzIoLnNlcnZlcl90b19jbG'
|
'Z2VzEmEKFG1lbW9yaWVzX3VwbG9hZF91cmxzGBIgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZX'
|
||||||
'llbnQuUmVzcG9uc2UuTWVtb3JpZXNVc2FnZUgAUg1tZW1vcmllc1VzYWdlQgQKAk9rQgoKCFJl'
|
'Nwb25zZS5NZW1vcmllc1VwbG9hZFVybHNIAFISbWVtb3JpZXNVcGxvYWRVcmxzEk4KDW1lbW9y'
|
||||||
'c3BvbnNl');
|
'aWVzX2xpc3QYEyABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzTGlzdE'
|
||||||
|
'gAUgxtZW1vcmllc0xpc3QSSwoMbWVtb3JpZXNfdXJsGBQgASgLMiYuc2VydmVyX3RvX2NsaWVu'
|
||||||
|
'dC5SZXNwb25zZS5NZW1vcmllc1VybEgAUgttZW1vcmllc1VybBJRCg5tZW1vcmllc191c2FnZR'
|
||||||
|
'gVIAEoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuTWVtb3JpZXNVc2FnZUgAUg1tZW1v'
|
||||||
|
'cmllc1VzYWdlQgQKAk9rQgoKCFJlc3BvbnNl');
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,11 @@ message TwonlySafeBackupEncrypted {
|
||||||
bytes mac = 1;
|
bytes mac = 1;
|
||||||
bytes nonce = 2;
|
bytes nonce = 2;
|
||||||
bytes cipher_text = 3;
|
bytes cipher_text = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CloudMediaBackupEncrypted {
|
||||||
|
string addition = 1;
|
||||||
|
bytes encrypted_media_key = 2; // Generated by Rust MainKey
|
||||||
|
bytes media_nonce = 3; // Nonce used for ChaCha20
|
||||||
|
bytes media_ciphertext = 4; // The actual media_ciphertext (including the Poly1305 MAC)
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +164,99 @@ class TwonlySafeBackupEncrypted extends $pb.GeneratedMessage {
|
||||||
void clearCipherText() => $_clearField(3);
|
void clearCipherText() => $_clearField(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CloudMediaBackupEncrypted extends $pb.GeneratedMessage {
|
||||||
|
factory CloudMediaBackupEncrypted({
|
||||||
|
$core.String? addition,
|
||||||
|
$core.List<$core.int>? encryptedMediaKey,
|
||||||
|
$core.List<$core.int>? mediaNonce,
|
||||||
|
$core.List<$core.int>? mediaCiphertext,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (addition != null) result.addition = addition;
|
||||||
|
if (encryptedMediaKey != null) result.encryptedMediaKey = encryptedMediaKey;
|
||||||
|
if (mediaNonce != null) result.mediaNonce = mediaNonce;
|
||||||
|
if (mediaCiphertext != null) result.mediaCiphertext = mediaCiphertext;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloudMediaBackupEncrypted._();
|
||||||
|
|
||||||
|
factory CloudMediaBackupEncrypted.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory CloudMediaBackupEncrypted.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'CloudMediaBackupEncrypted',
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOS(1, _omitFieldNames ? '' : 'addition')
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
2, _omitFieldNames ? '' : 'encryptedMediaKey', $pb.PbFieldType.OY)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
3, _omitFieldNames ? '' : 'mediaNonce', $pb.PbFieldType.OY)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
4, _omitFieldNames ? '' : 'mediaCiphertext', $pb.PbFieldType.OY)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
CloudMediaBackupEncrypted clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
CloudMediaBackupEncrypted copyWith(
|
||||||
|
void Function(CloudMediaBackupEncrypted) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as CloudMediaBackupEncrypted))
|
||||||
|
as CloudMediaBackupEncrypted;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static CloudMediaBackupEncrypted create() => CloudMediaBackupEncrypted._();
|
||||||
|
@$core.override
|
||||||
|
CloudMediaBackupEncrypted createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static CloudMediaBackupEncrypted getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<CloudMediaBackupEncrypted>(create);
|
||||||
|
static CloudMediaBackupEncrypted? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.String get addition => $_getSZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set addition($core.String value) => $_setString(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasAddition() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearAddition() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.List<$core.int> get encryptedMediaKey => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set encryptedMediaKey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasEncryptedMediaKey() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearEncryptedMediaKey() => $_clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.List<$core.int> get mediaNonce => $_getN(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set mediaNonce($core.List<$core.int> value) => $_setBytes(2, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasMediaNonce() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearMediaNonce() => $_clearField(3);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.List<$core.int> get mediaCiphertext => $_getN(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set mediaCiphertext($core.List<$core.int> value) => $_setBytes(3, value);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasMediaCiphertext() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearMediaCiphertext() => $_clearField(4);
|
||||||
|
}
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
const $core.bool _omitFieldNames =
|
||||||
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||||
const $core.bool _omitMessageNames =
|
const $core.bool _omitMessageNames =
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,27 @@ final $typed_data.Uint8List twonlySafeBackupEncryptedDescriptor =
|
||||||
$convert.base64Decode(
|
$convert.base64Decode(
|
||||||
'ChlUd29ubHlTYWZlQmFja3VwRW5jcnlwdGVkEhAKA21hYxgBIAEoDFIDbWFjEhQKBW5vbmNlGA'
|
'ChlUd29ubHlTYWZlQmFja3VwRW5jcnlwdGVkEhAKA21hYxgBIAEoDFIDbWFjEhQKBW5vbmNlGA'
|
||||||
'IgASgMUgVub25jZRIfCgtjaXBoZXJfdGV4dBgDIAEoDFIKY2lwaGVyVGV4dA==');
|
'IgASgMUgVub25jZRIfCgtjaXBoZXJfdGV4dBgDIAEoDFIKY2lwaGVyVGV4dA==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use cloudMediaBackupEncryptedDescriptor instead')
|
||||||
|
const CloudMediaBackupEncrypted$json = {
|
||||||
|
'1': 'CloudMediaBackupEncrypted',
|
||||||
|
'2': [
|
||||||
|
{'1': 'addition', '3': 1, '4': 1, '5': 9, '10': 'addition'},
|
||||||
|
{
|
||||||
|
'1': 'encrypted_media_key',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 12,
|
||||||
|
'10': 'encryptedMediaKey'
|
||||||
|
},
|
||||||
|
{'1': 'media_nonce', '3': 3, '4': 1, '5': 12, '10': 'mediaNonce'},
|
||||||
|
{'1': 'media_ciphertext', '3': 4, '4': 1, '5': 12, '10': 'mediaCiphertext'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `CloudMediaBackupEncrypted`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List cloudMediaBackupEncryptedDescriptor = $convert.base64Decode(
|
||||||
|
'ChlDbG91ZE1lZGlhQmFja3VwRW5jcnlwdGVkEhoKCGFkZGl0aW9uGAEgASgJUghhZGRpdGlvbh'
|
||||||
|
'IuChNlbmNyeXB0ZWRfbWVkaWFfa2V5GAIgASgMUhFlbmNyeXB0ZWRNZWRpYUtleRIfCgttZWRp'
|
||||||
|
'YV9ub25jZRgDIAEoDFIKbWVkaWFOb25jZRIpChBtZWRpYV9jaXBoZXJ0ZXh0GAQgASgMUg9tZW'
|
||||||
|
'RpYUNpcGhlcnRleHQ=');
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import 'package:twonly/src/services/api/server_messages.api.dart';
|
||||||
import 'package:twonly/src/services/api/utils.api.dart';
|
import 'package:twonly/src/services/api/utils.api.dart';
|
||||||
import 'package:twonly/src/services/flame.service.dart';
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/group.service.dart';
|
import 'package:twonly/src/services/group.service.dart';
|
||||||
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||||
|
|
@ -143,6 +144,7 @@ class ApiService {
|
||||||
unawaited(PasswordlessRecoveryService.performHeartbeat());
|
unawaited(PasswordlessRecoveryService.performHeartbeat());
|
||||||
|
|
||||||
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
|
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
|
||||||
|
memoriesCloudService.init();
|
||||||
|
|
||||||
if (userService.currentUser.userStudyParticipantsToken != null) {
|
if (userService.currentUser.userStudyParticipantsToken != null) {
|
||||||
// In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection
|
// In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection
|
||||||
|
|
@ -710,6 +712,75 @@ class ApiService {
|
||||||
return sendRequestSync(req);
|
return sendRequestSync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<server.Response_MemoriesUploadUrls?> requestMemoriesUpload(
|
||||||
|
int sizeBytes,
|
||||||
|
DateTime originalDate,
|
||||||
|
String mediaId,
|
||||||
|
) async {
|
||||||
|
final get = ApplicationData_RequestMemoriesUpload()
|
||||||
|
..size = Int64(sizeBytes)
|
||||||
|
..originalDate = Int64(originalDate.millisecondsSinceEpoch)
|
||||||
|
..mediaId = mediaId;
|
||||||
|
final appData = ApplicationData()..requestMemoriesUpload = get;
|
||||||
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
|
final res = await sendRequestSync(req);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
final ok = res.value as server.Response_Ok;
|
||||||
|
if (ok.hasMemoriesUploadUrls()) {
|
||||||
|
return ok.memoriesUploadUrls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<server.Response_MemoriesUsage?> getMemoriesUsage() async {
|
||||||
|
final appData = ApplicationData()
|
||||||
|
..getMemoriesUsage = ApplicationData_GetMemoriesUsage();
|
||||||
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
|
final res = await sendRequestSync(req);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
final ok = res.value as server.Response_Ok;
|
||||||
|
if (ok.hasMemoriesUsage()) {
|
||||||
|
return ok.memoriesUsage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<server.Response_MemoriesUrl?> getMemoriesUrl(
|
||||||
|
String mediaId,
|
||||||
|
bool thumbnail,
|
||||||
|
) async {
|
||||||
|
final appData = ApplicationData()
|
||||||
|
..getMemoriesUrl = ApplicationData_GetMemoriesUrl(
|
||||||
|
mediaId: mediaId,
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
);
|
||||||
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
|
final res = await sendRequestSync(req);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
final ok = res.value as server.Response_Ok;
|
||||||
|
if (ok.hasMemoriesUrl()) {
|
||||||
|
return ok.memoriesUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result> confirmMemoriesUpload(String mediaId) async {
|
||||||
|
final get = ApplicationData_ConfirmMemoriesUpload()..mediaId = mediaId;
|
||||||
|
final appData = ApplicationData()..confirmMemoriesUpload = get;
|
||||||
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
|
return sendRequestSync(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result> deleteMemory(String mediaId) async {
|
||||||
|
final get = ApplicationData_DeleteMemory()..mediaId = mediaId;
|
||||||
|
final appData = ApplicationData()..deleteMemory = get;
|
||||||
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
|
return sendRequestSync(req);
|
||||||
|
}
|
||||||
|
|
||||||
Future<int?> getUserIdFromUsername(String username) async {
|
Future<int?> getUserIdFromUsername(String username) async {
|
||||||
final appData = Handshake(
|
final appData = Handshake(
|
||||||
getUseridByUsername: Handshake_GetUserIdByUsername(username: username),
|
getUseridByUsername: Handshake_GetUserIdByUsername(username: username),
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ Future<void> initFileDownloader() async {
|
||||||
if (update.task.taskId.contains('download_')) {
|
if (update.task.taskId.contains('download_')) {
|
||||||
await handleDownloadStatusUpdate(update);
|
await handleDownloadStatusUpdate(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.task.taskId.contains('backup_')) {
|
if (update.task.taskId.contains('backup_')) {
|
||||||
await BackupService.handleBackupStatusUpdate(
|
await BackupService.handleBackupStatusUpdate(
|
||||||
update.task.taskId,
|
update.task.taskId,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/compression.service.dart';
|
import 'package:twonly/src/services/mediafiles/compression.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart'
|
||||||
|
show MemoriesCloudService;
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
|
@ -220,6 +222,7 @@ class MediaFileService {
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
if (mediaFile.stored &&
|
if (mediaFile.stored &&
|
||||||
|
mediaFile.cloudState == CloudState.none &&
|
||||||
mediaFile.createdAt.isBefore(
|
mediaFile.createdAt.isBefore(
|
||||||
clock.now().subtract(const Duration(days: 30)),
|
clock.now().subtract(const Duration(days: 30)),
|
||||||
)) {
|
)) {
|
||||||
|
|
@ -335,6 +338,7 @@ class MediaFileService {
|
||||||
unawaited(createThumbnail());
|
unawaited(createThumbnail());
|
||||||
await calculateAndSaveSize();
|
await calculateAndSaveSize();
|
||||||
await hashMediaFile();
|
await hashMediaFile();
|
||||||
|
await MemoriesCloudService().checkUploads();
|
||||||
// updateFromDb is done in hashStoredMedia()
|
// updateFromDb is done in hashStoredMedia()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ Future<bool> createThumbnailsForImage(
|
||||||
destinationFile.absolute.path,
|
destinationFile.absolute.path,
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
minHeight: 300,
|
minHeight: 300,
|
||||||
quality: 100,
|
quality: 50,
|
||||||
format: CompressFormat.webp,
|
format: CompressFormat.webp,
|
||||||
);
|
);
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:blurhash_dart/blurhash_dart.dart' as bh;
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
|
@ -190,7 +192,8 @@ class MemoriesService {
|
||||||
|
|
||||||
return MemoriesState(
|
return MemoriesState(
|
||||||
filesToMigrate: filesToMigrate,
|
filesToMigrate: filesToMigrate,
|
||||||
totalFilesToMigrate: filesToMigrate, // Reset total when computing new state? No, keep existing total if migrating.
|
totalFilesToMigrate:
|
||||||
|
filesToMigrate, // Reset total when computing new state? No, keep existing total if migrating.
|
||||||
galleryItems: tempGalleryItems,
|
galleryItems: tempGalleryItems,
|
||||||
months: tempMonths,
|
months: tempMonths,
|
||||||
orderedByMonth: tempOrderedByMonth,
|
orderedByMonth: tempOrderedByMonth,
|
||||||
|
|
@ -280,6 +283,27 @@ class MemoriesService {
|
||||||
if (mediaService.mediaFile.sizeInBytes == null) {
|
if (mediaService.mediaFile.sizeInBytes == null) {
|
||||||
await mediaService.calculateAndSaveSize();
|
await mediaService.calculateAndSaveSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mediaService.mediaFile.blurhash == null) {
|
||||||
|
try {
|
||||||
|
final imageFile = mediaService.thumbnailPath.existsSync()
|
||||||
|
? mediaService.thumbnailPath
|
||||||
|
: mediaService.originalPath;
|
||||||
|
if (imageFile.existsSync()) {
|
||||||
|
final bytes = await imageFile.readAsBytes();
|
||||||
|
final image = img.decodeImage(bytes);
|
||||||
|
if (image != null) {
|
||||||
|
final blurhash = bh.BlurHash.encode(image).hash;
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(blurhash: Value(blurhash)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Error generating blurhash for ${mediaFile.mediaId}: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Error in background processing of ${mediaFile.mediaId}: $e',
|
'Error in background processing of ${mediaFile.mediaId}: $e',
|
||||||
|
|
|
||||||
394
lib/src/services/memories/memories_cloud.service.dart
Normal file
394
lib/src/services/memories/memories_cloud.service.dart
Normal file
|
|
@ -0,0 +1,394 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/key_manager.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/model/protobuf/client/generated/backup.pb.dart';
|
||||||
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
class MemoriesBackupProgress {
|
||||||
|
MemoriesBackupProgress({
|
||||||
|
required this.totalPending,
|
||||||
|
required this.currentUploaded,
|
||||||
|
required this.currentUploadProgress,
|
||||||
|
this.currentMediaId,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int totalPending;
|
||||||
|
final int currentUploaded;
|
||||||
|
final double currentUploadProgress;
|
||||||
|
final String? currentMediaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProgressMultipartRequest extends http.MultipartRequest {
|
||||||
|
ProgressMultipartRequest(super.method, super.url, {this.onProgress});
|
||||||
|
|
||||||
|
final void Function(int bytes, int totalBytes)? onProgress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
http.ByteStream finalize() {
|
||||||
|
final byteStream = super.finalize();
|
||||||
|
if (onProgress == null) return byteStream;
|
||||||
|
|
||||||
|
final total = contentLength;
|
||||||
|
var bytes = 0;
|
||||||
|
|
||||||
|
final transformer = StreamTransformer<List<int>, List<int>>.fromHandlers(
|
||||||
|
handleData: (data, sink) {
|
||||||
|
bytes += data.length;
|
||||||
|
onProgress!(bytes, total);
|
||||||
|
sink.add(data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.ByteStream(byteStream.transform(transformer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MemoriesCloudService {
|
||||||
|
Timer? _timer;
|
||||||
|
bool _isProcessing = false;
|
||||||
|
|
||||||
|
final _progressController =
|
||||||
|
StreamController<MemoriesBackupProgress>.broadcast();
|
||||||
|
Stream<MemoriesBackupProgress> get progressStream =>
|
||||||
|
_progressController.stream;
|
||||||
|
|
||||||
|
MemoriesBackupProgress? _currentProgress;
|
||||||
|
MemoriesBackupProgress? get currentProgress => _currentProgress;
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
_timer = Timer.periodic(const Duration(minutes: 5), (_) {
|
||||||
|
checkUploads();
|
||||||
|
});
|
||||||
|
// Run immediately
|
||||||
|
Future.delayed(const Duration(seconds: 10), checkUploads);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_progressController.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateProgress({
|
||||||
|
required int totalPending,
|
||||||
|
required int currentUploaded,
|
||||||
|
required double currentUploadProgress,
|
||||||
|
String? currentMediaId,
|
||||||
|
}) {
|
||||||
|
_currentProgress = MemoriesBackupProgress(
|
||||||
|
totalPending: totalPending,
|
||||||
|
currentUploaded: currentUploaded,
|
||||||
|
currentUploadProgress: currentUploadProgress,
|
||||||
|
currentMediaId: currentMediaId,
|
||||||
|
);
|
||||||
|
_progressController.add(_currentProgress!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> checkUploads() async {
|
||||||
|
if (_isProcessing || !userService.currentUser.isBackupEnabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final memories = await twonlyDB.mediaFilesDao.getMemoriesToBackup();
|
||||||
|
if (memories.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isProcessing = true;
|
||||||
|
|
||||||
|
final total = memories.length;
|
||||||
|
var uploaded = 0;
|
||||||
|
|
||||||
|
_updateProgress(
|
||||||
|
totalPending: total,
|
||||||
|
currentUploaded: uploaded,
|
||||||
|
currentUploadProgress: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final mediaFile in memories) {
|
||||||
|
_updateProgress(
|
||||||
|
totalPending: total,
|
||||||
|
currentUploaded: uploaded,
|
||||||
|
currentUploadProgress: 0,
|
||||||
|
currentMediaId: mediaFile.mediaId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final success = await _backupMemory(mediaFile, (progress) {
|
||||||
|
_updateProgress(
|
||||||
|
totalPending: total,
|
||||||
|
currentUploaded: uploaded,
|
||||||
|
currentUploadProgress: progress,
|
||||||
|
currentMediaId: mediaFile.mediaId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
uploaded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateProgress(
|
||||||
|
totalPending: total,
|
||||||
|
currentUploaded: uploaded,
|
||||||
|
currentUploadProgress: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Error in MemoriesCloudService.checkUploads: $e');
|
||||||
|
} finally {
|
||||||
|
_currentProgress = null;
|
||||||
|
_isProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> downloadThumbnail(MediaFileService media) async {
|
||||||
|
final urls = await apiService.getMemoriesUrl(media.mediaFile.mediaId, true);
|
||||||
|
if (urls == null || !urls.hasFullDownloadUrl()) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.get(Uri.parse(urls.fullDownloadUrl));
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return await _decryptFile(response.bodyBytes, media.thumbnailPath);
|
||||||
|
} else {
|
||||||
|
Log.warn(
|
||||||
|
'Failed to download thumbnai statuscode ${response.statusCode}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.warn(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _backupMemory(
|
||||||
|
MediaFile mediaFile,
|
||||||
|
void Function(double progress) onProgress,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final ms = MediaFileService(mediaFile);
|
||||||
|
|
||||||
|
if (!ms.storedPath.existsSync()) return false;
|
||||||
|
|
||||||
|
if (!mediaFile.hasThumbnail) {
|
||||||
|
await MediaFileService(mediaFile).createThumbnail();
|
||||||
|
}
|
||||||
|
|
||||||
|
final sizeBytes = ms.storedPath.lengthSync();
|
||||||
|
final urls = await apiService.requestMemoriesUpload(
|
||||||
|
sizeBytes,
|
||||||
|
mediaFile.createdAt,
|
||||||
|
mediaFile.mediaId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (urls == null) {
|
||||||
|
Log.error('Could not get upload URLs for memory ${mediaFile.mediaId}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
const MediaFilesCompanion(
|
||||||
|
cloudState: Value(CloudState.pending),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final mediaKey = getRandomUint8List(32);
|
||||||
|
final encryptedMediaKey = await RustKeyManager.encryptCloudMediaKey(
|
||||||
|
mediaKey: mediaKey,
|
||||||
|
addition: 'app',
|
||||||
|
);
|
||||||
|
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
|
||||||
|
// 1. Upload thumbnail if exists
|
||||||
|
if (ms.thumbnailPath.existsSync() && urls.hasThumbnailUpload()) {
|
||||||
|
final thumbFile = await _encryptFile(
|
||||||
|
ms.thumbnailPath,
|
||||||
|
mediaKey,
|
||||||
|
encryptedMediaKey,
|
||||||
|
tempDir,
|
||||||
|
'thumb_${mediaFile.mediaId}',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await _uploadToS3(urls.thumbnailUpload, thumbFile, (_) {});
|
||||||
|
} finally {
|
||||||
|
if (thumbFile.existsSync()) {
|
||||||
|
thumbFile.deleteSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Upload full media if exists
|
||||||
|
if (urls.hasFullUpload()) {
|
||||||
|
final fullFile = await _encryptFile(
|
||||||
|
ms.storedPath,
|
||||||
|
mediaKey,
|
||||||
|
encryptedMediaKey,
|
||||||
|
tempDir,
|
||||||
|
'full_${mediaFile.mediaId}',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await _uploadToS3(urls.fullUpload, fullFile, onProgress);
|
||||||
|
} finally {
|
||||||
|
if (fullFile.existsSync()) {
|
||||||
|
fullFile.deleteSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Confirm upload
|
||||||
|
final confirmRes = await apiService.confirmMemoriesUpload(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
);
|
||||||
|
if (confirmRes.isSuccess) {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
const MediaFilesCompanion(
|
||||||
|
cloudState: Value(CloudState.uploaded),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Log.info(
|
||||||
|
'Cloud backup complete and confirmed for ${mediaFile.mediaId}',
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw Exception('Server confirmation failed: ${confirmRes.error}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Error backing up memory ${mediaFile.mediaId}: $e');
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
const MediaFilesCompanion(
|
||||||
|
cloudState: Value(CloudState.none),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _uploadToS3(
|
||||||
|
server.Response_PresignedPost presignedPost,
|
||||||
|
File file,
|
||||||
|
void Function(double progress) onProgress,
|
||||||
|
) async {
|
||||||
|
final request = ProgressMultipartRequest(
|
||||||
|
'POST',
|
||||||
|
Uri.parse(presignedPost.url),
|
||||||
|
onProgress: (bytes, total) {
|
||||||
|
if (total > 0) {
|
||||||
|
onProgress(bytes / total);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
request.fields.addAll(presignedPost.fields);
|
||||||
|
request.files.add(
|
||||||
|
await http.MultipartFile.fromPath(
|
||||||
|
'file',
|
||||||
|
file.path,
|
||||||
|
filename: 'file',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final streamedResponse = await request.send();
|
||||||
|
final response = await http.Response.fromStream(streamedResponse);
|
||||||
|
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||||
|
throw Exception(
|
||||||
|
'S3 upload failed: ${response.statusCode} - ${response.body}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File> _encryptFile(
|
||||||
|
File inputFile,
|
||||||
|
Uint8List mediaKey,
|
||||||
|
List<int> encryptedMediaKey,
|
||||||
|
Directory tempDir,
|
||||||
|
String prefix,
|
||||||
|
) async {
|
||||||
|
final dataToEncrypt = await inputFile.readAsBytes();
|
||||||
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
|
final nonce = chacha20.newNonce();
|
||||||
|
|
||||||
|
final secretBox = await chacha20.encrypt(
|
||||||
|
dataToEncrypt,
|
||||||
|
secretKey: SecretKey(mediaKey),
|
||||||
|
nonce: nonce,
|
||||||
|
);
|
||||||
|
|
||||||
|
final cipherTextWithMac = Uint8List.fromList(
|
||||||
|
secretBox.cipherText + secretBox.mac.bytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
final payload = CloudMediaBackupEncrypted()
|
||||||
|
..addition = 'app'
|
||||||
|
..encryptedMediaKey = encryptedMediaKey
|
||||||
|
..mediaNonce = nonce
|
||||||
|
..mediaCiphertext = cipherTextWithMac;
|
||||||
|
|
||||||
|
final outFile = File('${tempDir.path}/$prefix.encrypted');
|
||||||
|
await outFile.writeAsBytes(payload.writeToBuffer());
|
||||||
|
return outFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> _decryptFile(
|
||||||
|
Uint8List encryptedData,
|
||||||
|
File outFile,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
// 2. Parse the protobuf payload FIRST to access the key and addition
|
||||||
|
final payload = CloudMediaBackupEncrypted.fromBuffer(encryptedData);
|
||||||
|
|
||||||
|
// 3. Get the media key using the encrypted key and addition from the payload
|
||||||
|
final mediaKey = await RustKeyManager.decryptCloudMediaKey(
|
||||||
|
encryptedMediaKey: payload.encryptedMediaKey,
|
||||||
|
addition: payload.addition,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Extract the concatenated ciphertext + MAC, and the nonce
|
||||||
|
final nonce = payload.mediaNonce;
|
||||||
|
final cipherTextWithMac = payload.mediaCiphertext;
|
||||||
|
|
||||||
|
// 5. Separate the ciphertext and the MAC (Poly1305 MAC is always 16 bytes)
|
||||||
|
const macLength = 16;
|
||||||
|
final cipherTextLength = cipherTextWithMac.length - macLength;
|
||||||
|
|
||||||
|
final cipherText = cipherTextWithMac.sublist(0, cipherTextLength);
|
||||||
|
final macBytes = cipherTextWithMac.sublist(cipherTextLength);
|
||||||
|
|
||||||
|
// 6. Reconstruct the SecretBox
|
||||||
|
final secretBox = SecretBox(
|
||||||
|
cipherText,
|
||||||
|
nonce: nonce,
|
||||||
|
mac: Mac(macBytes),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Decrypt the data using the newly retrieved media key
|
||||||
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
|
final decryptedBytes = await chacha20.decrypt(
|
||||||
|
secretBox,
|
||||||
|
secretKey: SecretKey(mediaKey),
|
||||||
|
);
|
||||||
|
|
||||||
|
await outFile.writeAsBytes(decryptedBytes);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final memoriesCloudService = MemoriesCloudService();
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/visual/components/selectable_thumbnail.comp.dart';
|
import 'package:twonly/src/visual/components/selectable_thumbnail.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/memories/components/memory_transition_painter.dart';
|
import 'package:twonly/src/visual/views/memories/components/memory_transition_painter.dart';
|
||||||
|
|
||||||
|
|
@ -31,8 +36,10 @@ class MemoriesThumbnailComp extends StatefulWidget {
|
||||||
|
|
||||||
class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
ImageProvider? _imageProvider;
|
ImageProvider? _imageProvider;
|
||||||
|
File? _selectedImageFile;
|
||||||
ImageStream? _imageStream;
|
ImageStream? _imageStream;
|
||||||
ImageInfo? _imageInfo;
|
ImageInfo? _imageInfo;
|
||||||
|
int _retries = 0;
|
||||||
late final ImageStreamListener _listener;
|
late final ImageStreamListener _listener;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -52,6 +59,7 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_imageProvider = null;
|
_imageProvider = null;
|
||||||
_imageInfo = null;
|
_imageInfo = null;
|
||||||
|
_selectedImageFile = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -60,6 +68,7 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resolveImage() {
|
void _resolveImage() {
|
||||||
|
if (_retries > 3) return;
|
||||||
final media = widget.galleryItem.mediaService;
|
final media = widget.galleryItem.mediaService;
|
||||||
final hasThumbnail =
|
final hasThumbnail =
|
||||||
media.thumbnailPath.existsSync() &&
|
media.thumbnailPath.existsSync() &&
|
||||||
|
|
@ -70,17 +79,33 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
media.mediaFile.type == MediaType.image ||
|
media.mediaFile.type == MediaType.image ||
|
||||||
media.mediaFile.type == MediaType.gif;
|
media.mediaFile.type == MediaType.gif;
|
||||||
|
|
||||||
|
_selectedImageFile = null;
|
||||||
|
|
||||||
if (hasThumbnail) {
|
if (hasThumbnail) {
|
||||||
_imageProvider = FileImage(media.thumbnailPath);
|
_imageProvider = FileImage(media.thumbnailPath);
|
||||||
|
_selectedImageFile = media.thumbnailPath;
|
||||||
} else if (hasStored && isImageOrGif) {
|
} else if (hasStored && isImageOrGif) {
|
||||||
_imageProvider = FileImage(media.storedPath);
|
_imageProvider = FileImage(media.storedPath);
|
||||||
|
_selectedImageFile = media.storedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasThumbnail) {
|
||||||
|
if (hasStored) {
|
||||||
|
media.createThumbnail();
|
||||||
|
} else {
|
||||||
|
MemoriesCloudService.downloadThumbnail(media).then((success) {
|
||||||
|
if (mounted && success) {
|
||||||
|
_resolveImage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_imageProvider != null) {
|
if (_imageProvider != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final config = createLocalImageConfiguration(context);
|
final config = createLocalImageConfiguration(context);
|
||||||
_imageStream = _imageProvider!.resolve(config);
|
_imageStream = _imageProvider?.resolve(config);
|
||||||
_imageStream!.addListener(_listener);
|
_imageStream!.addListener(_listener);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +118,8 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
widget.galleryItem.mediaService.mediaFile.mediaId) {
|
widget.galleryItem.mediaService.mediaFile.mediaId) {
|
||||||
_imageStream?.removeListener(_listener);
|
_imageStream?.removeListener(_listener);
|
||||||
_imageInfo = null;
|
_imageInfo = null;
|
||||||
|
_retries = 0;
|
||||||
|
_selectedImageFile = null;
|
||||||
_resolveImage();
|
_resolveImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +175,14 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
gaplessPlayback: true,
|
gaplessPlayback: true,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
if (error.toString().contains('Invalid image data')) {
|
||||||
|
if (_selectedImageFile != null) {
|
||||||
|
_selectedImageFile?.deleteSync();
|
||||||
|
_retries++;
|
||||||
|
_resolveImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.warn(error);
|
||||||
return ColoredBox(
|
return ColoredBox(
|
||||||
color: Colors.grey.shade200,
|
color: Colors.grey.shade200,
|
||||||
child: const Center(
|
child: const Center(
|
||||||
|
|
@ -159,6 +194,11 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
else if (media.mediaFile.blurhash != null)
|
||||||
|
BlurHash(
|
||||||
|
hash: media.mediaFile.blurhash!,
|
||||||
|
optimizationMode: BlurHashOptimizationMode.approximation,
|
||||||
|
)
|
||||||
else
|
else
|
||||||
ColoredBox(
|
ColoredBox(
|
||||||
color: Colors.grey.shade200,
|
color: Colors.grey.shade200,
|
||||||
|
|
@ -195,6 +235,32 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (media.mediaFile.cloudState == CloudState.pending)
|
||||||
|
const Positioned(
|
||||||
|
top: 6,
|
||||||
|
right: 6,
|
||||||
|
child: Icon(
|
||||||
|
Icons.cloud_upload_outlined,
|
||||||
|
color: Colors.white70,
|
||||||
|
size: 16,
|
||||||
|
shadows: [
|
||||||
|
Shadow(color: Colors.black54, blurRadius: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (media.mediaFile.cloudState == CloudState.uploaded)
|
||||||
|
const Positioned(
|
||||||
|
top: 6,
|
||||||
|
right: 6,
|
||||||
|
child: Icon(
|
||||||
|
Icons.cloud_done_outlined,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
shadows: [
|
||||||
|
Shadow(color: Colors.black54, blurRadius: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ class MemoriesView extends StatefulWidget {
|
||||||
State<MemoriesView> createState() => MemoriesViewState();
|
State<MemoriesView> createState() => MemoriesViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClientMixin<MemoriesView> {
|
class MemoriesViewState extends State<MemoriesView>
|
||||||
|
with AutomaticKeepAliveClientMixin<MemoriesView> {
|
||||||
late final MemoriesService _service;
|
late final MemoriesService _service;
|
||||||
final ValueNotifier<String?> _activeMediaIdNotifier = ValueNotifier(null);
|
final ValueNotifier<String?> _activeMediaIdNotifier = ValueNotifier(null);
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
@ -31,11 +32,27 @@ class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClien
|
||||||
bool _filterFavoritesOnly = false;
|
bool _filterFavoritesOnly = false;
|
||||||
bool get _selectionMode => _selectedMediaIds.isNotEmpty;
|
bool get _selectionMode => _selectedMediaIds.isNotEmpty;
|
||||||
|
|
||||||
|
bool _isUsageLimitReached = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_service = MemoriesService();
|
_service = MemoriesService();
|
||||||
_activeMediaIdNotifier.addListener(_onActiveMediaChanged);
|
_activeMediaIdNotifier.addListener(_onActiveMediaChanged);
|
||||||
|
_checkUsage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkUsage() async {
|
||||||
|
final usage = await apiService.getMemoriesUsage();
|
||||||
|
if (usage != null &&
|
||||||
|
usage.maxBytes > 0 &&
|
||||||
|
usage.currentBytes >= usage.maxBytes) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isUsageLimitReached = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -255,17 +272,56 @@ class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClien
|
||||||
|
|
||||||
Future<void> _batchDelete() async {
|
Future<void> _batchDelete() async {
|
||||||
final count = _selectedMediaIds.length;
|
final count = _selectedMediaIds.length;
|
||||||
final confirmed = await showAlertDialog(
|
|
||||||
context,
|
|
||||||
context.lang.deleteImageTitle,
|
|
||||||
context.lang.deleteMemoriesBody(count),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
final items = _service.currentState.galleryItems;
|
final items = _service.currentState.galleryItems;
|
||||||
final selectedList = _selectedMediaIds.toList();
|
final selectedList = _selectedMediaIds.toList();
|
||||||
|
|
||||||
|
var hasCloudBackup = false;
|
||||||
|
for (final id in selectedList) {
|
||||||
|
final item = items
|
||||||
|
.where((e) => e.mediaService.mediaFile.mediaId == id)
|
||||||
|
.firstOrNull;
|
||||||
|
if (item != null &&
|
||||||
|
item.mediaService.mediaFile.cloudState != CloudState.none) {
|
||||||
|
hasCloudBackup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool? deleteCompletely;
|
||||||
|
if (hasCloudBackup) {
|
||||||
|
deleteCompletely = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(context.lang.deleteImageTitle),
|
||||||
|
content: Text(context.lang.deleteMemoriesBody(count)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(context.lang.galleryCancel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: const Text('Local Only'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
|
child: const Text('Completely'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final confirmed = await showAlertDialog(
|
||||||
|
context,
|
||||||
|
context.lang.deleteImageTitle,
|
||||||
|
context.lang.deleteMemoriesBody(count),
|
||||||
|
);
|
||||||
|
if (confirmed) deleteCompletely = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteCompletely == null) return;
|
||||||
|
|
||||||
await _showProgressDialog(
|
await _showProgressDialog(
|
||||||
'Deleting memories...',
|
'Deleting memories...',
|
||||||
(setProgress) async {
|
(setProgress) async {
|
||||||
|
|
@ -275,9 +331,14 @@ class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClien
|
||||||
.where((e) => e.mediaService.mediaFile.mediaId == mediaId)
|
.where((e) => e.mediaService.mediaFile.mediaId == mediaId)
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
item.mediaService.fullMediaRemoval();
|
if (deleteCompletely!) {
|
||||||
|
item.mediaService.fullMediaRemoval();
|
||||||
|
await apiService.deleteMemory(mediaId);
|
||||||
|
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaId);
|
||||||
|
} else {
|
||||||
|
item.mediaService.storedPath.deleteSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaId);
|
|
||||||
setProgress((i + 1) / selectedList.length);
|
setProgress((i + 1) / selectedList.length);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -406,10 +467,8 @@ class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClien
|
||||||
Text(
|
Text(
|
||||||
context.lang.memoriesEmpty,
|
context.lang.memoriesEmpty,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
color: Colors.grey,
|
||||||
color: Colors.grey.shade600,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -446,156 +505,201 @@ class MemoriesViewState extends State<MemoriesView> with AutomaticKeepAliveClien
|
||||||
orderedByMonth = filteredOrdered;
|
orderedByMonth = filteredOrdered;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LayoutBuilder(
|
return Column(
|
||||||
builder: (context, constraints) {
|
children: [
|
||||||
return DraggableScrollbar(
|
if (_isUsageLimitReached)
|
||||||
controller: _scrollController,
|
Container(
|
||||||
labelBuilder: (offset) {
|
color: Colors.redAccent,
|
||||||
final state = _service.currentState;
|
width: double.infinity,
|
||||||
if (state.isEmpty || state.months.isEmpty) return null;
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Cloud backup limit reached! Please upgrade your plan or free up space.',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return DraggableScrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
labelBuilder: (offset) {
|
||||||
|
final state = _service.currentState;
|
||||||
|
if (state.isEmpty || state.months.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Simple heuristic to find month by offset
|
// Simple heuristic to find month by offset
|
||||||
double currentOffset = 56;
|
double currentOffset = 56;
|
||||||
if (state.galleryItemsLastYears.isNotEmpty) {
|
if (state.galleryItemsLastYears.isNotEmpty) {
|
||||||
currentOffset += 220;
|
currentOffset += 220;
|
||||||
}
|
}
|
||||||
|
|
||||||
final screenWidth = MediaQuery.sizeOf(context).width;
|
final screenWidth = MediaQuery.sizeOf(
|
||||||
final itemWidth = (screenWidth - 8) / 4;
|
context,
|
||||||
final itemHeight = itemWidth * (16 / 9);
|
).width;
|
||||||
final rowHeight = itemHeight + 2;
|
final itemWidth = (screenWidth - 8) / 4;
|
||||||
|
final itemHeight = itemWidth * (16 / 9);
|
||||||
|
final rowHeight = itemHeight + 2;
|
||||||
|
|
||||||
for (final month in state.months) {
|
for (final month in state.months) {
|
||||||
final indices = state.orderedByMonth[month]!;
|
final indices = state.orderedByMonth[month]!;
|
||||||
final totalRows = (indices.length + 3) ~/ 4;
|
final totalRows = (indices.length + 3) ~/ 4;
|
||||||
final monthHeight = 44 + (totalRows * rowHeight);
|
final monthHeight = 44 + (totalRows * rowHeight);
|
||||||
|
|
||||||
if (offset < currentOffset + monthHeight) {
|
if (offset < currentOffset + monthHeight) {
|
||||||
return month;
|
return month;
|
||||||
}
|
}
|
||||||
currentOffset += monthHeight;
|
currentOffset += monthHeight;
|
||||||
}
|
}
|
||||||
return state.months.last;
|
return state.months.last;
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Memories',
|
'Memories',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
floating: true,
|
|
||||||
snap: true,
|
|
||||||
elevation: 0,
|
|
||||||
backgroundColor: context.color.surface,
|
|
||||||
actions: [
|
|
||||||
if (state.isLoading)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
),
|
||||||
child: Center(
|
floating: true,
|
||||||
child: Tooltip(
|
snap: true,
|
||||||
message: context.lang.migrationOfMemories(
|
elevation: 0,
|
||||||
state.filesToMigrate,
|
backgroundColor: context.color.surface,
|
||||||
),
|
actions: [
|
||||||
child: SizedBox(
|
if (state.isLoading)
|
||||||
width: 20,
|
Padding(
|
||||||
height: 20,
|
padding: const EdgeInsets.symmetric(
|
||||||
child: CircularProgressIndicator(
|
horizontal: 16,
|
||||||
value: state.migrationProgress,
|
),
|
||||||
strokeWidth: 2.5,
|
child: Center(
|
||||||
valueColor: AlwaysStoppedAnimation(
|
child: Tooltip(
|
||||||
context.color.primary,
|
message: context.lang
|
||||||
|
.migrationOfMemories(
|
||||||
|
state.filesToMigrate,
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: state.migrationProgress,
|
||||||
|
strokeWidth: 2.5,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation(
|
||||||
|
context.color.primary,
|
||||||
|
),
|
||||||
|
backgroundColor: context
|
||||||
|
.color
|
||||||
|
.primary
|
||||||
|
.withValues(alpha: 0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: context.color.primary
|
),
|
||||||
.withValues(alpha: 0.2),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_filterFavoritesOnly
|
||||||
|
? Icons.favorite
|
||||||
|
: Icons.favorite_border,
|
||||||
|
color: _filterFavoritesOnly
|
||||||
|
? Colors.redAccent
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_filterFavoritesOnly =
|
||||||
|
!_filterFavoritesOnly;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tooltip: _filterFavoritesOnly
|
||||||
|
? 'Show all'
|
||||||
|
: 'Show favorites only',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MemoriesFlashbackBannerComp(
|
||||||
|
lastYears: lastYears,
|
||||||
|
onOpenFlashback: (items, idx) =>
|
||||||
|
_openViewer(items, idx, isFlashback: true),
|
||||||
|
),
|
||||||
|
for (final month in months) ...[
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
8,
|
||||||
|
12,
|
||||||
|
8,
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: Text(
|
||||||
|
month,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SliverGrid(
|
||||||
IconButton(
|
gridDelegate:
|
||||||
icon: Icon(
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
_filterFavoritesOnly
|
crossAxisCount: 4,
|
||||||
? Icons.favorite
|
mainAxisSpacing: 2,
|
||||||
: Icons.favorite_border,
|
crossAxisSpacing: 2,
|
||||||
color: _filterFavoritesOnly
|
childAspectRatio: 9 / 16,
|
||||||
? Colors.redAccent
|
),
|
||||||
: null,
|
delegate: SliverChildBuilderDelegate(
|
||||||
),
|
(context, idx) {
|
||||||
onPressed: () {
|
final globalIndex =
|
||||||
setState(() {
|
orderedByMonth[month]![idx];
|
||||||
_filterFavoritesOnly = !_filterFavoritesOnly;
|
final item =
|
||||||
});
|
state.galleryItems[globalIndex];
|
||||||
},
|
final mediaId =
|
||||||
tooltip: _filterFavoritesOnly
|
item.mediaService.mediaFile.mediaId;
|
||||||
? 'Show all'
|
final isSelected = _selectedMediaIds
|
||||||
: 'Show favorites only',
|
.contains(
|
||||||
),
|
mediaId,
|
||||||
],
|
);
|
||||||
),
|
|
||||||
MemoriesFlashbackBannerComp(
|
|
||||||
lastYears: lastYears,
|
|
||||||
onOpenFlashback: (items, idx) =>
|
|
||||||
_openViewer(items, idx, isFlashback: true),
|
|
||||||
),
|
|
||||||
for (final month in months) ...[
|
|
||||||
SliverPadding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(8, 12, 8, 6),
|
|
||||||
sliver: SliverToBoxAdapter(
|
|
||||||
child: Text(
|
|
||||||
month,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverGrid(
|
|
||||||
gridDelegate:
|
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 4,
|
|
||||||
mainAxisSpacing: 2,
|
|
||||||
crossAxisSpacing: 2,
|
|
||||||
childAspectRatio: 9 / 16,
|
|
||||||
),
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, idx) {
|
|
||||||
final globalIndex = orderedByMonth[month]![idx];
|
|
||||||
final item = state.galleryItems[globalIndex];
|
|
||||||
final mediaId =
|
|
||||||
item.mediaService.mediaFile.mediaId;
|
|
||||||
final isSelected = _selectedMediaIds.contains(
|
|
||||||
mediaId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return MemoriesThumbnailComp(
|
return MemoriesThumbnailComp(
|
||||||
galleryItem: item,
|
galleryItem: item,
|
||||||
index: globalIndex,
|
index: globalIndex,
|
||||||
selectionMode: _selectionMode,
|
selectionMode: _selectionMode,
|
||||||
isSelected: isSelected,
|
isSelected: isSelected,
|
||||||
activeMediaIdNotifier: _activeMediaIdNotifier,
|
activeMediaIdNotifier:
|
||||||
onLongPress: () => _onLongPressItem(mediaId),
|
_activeMediaIdNotifier,
|
||||||
onTap: () => _onTapItem(mediaId, globalIndex),
|
onLongPress: () =>
|
||||||
);
|
_onLongPressItem(mediaId),
|
||||||
},
|
onTap: () =>
|
||||||
childCount: orderedByMonth[month]!.length,
|
_onTapItem(mediaId, globalIndex),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
|
childCount: orderedByMonth[month]!.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom +
|
||||||
|
150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
SliverPadding(
|
},
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 150,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
|
@ -313,6 +314,9 @@ class _SynchronizedImageViewerScreenState
|
||||||
var filePath = item.mediaService.storedPath;
|
var filePath = item.mediaService.storedPath;
|
||||||
if (!filePath.existsSync()) {
|
if (!filePath.existsSync()) {
|
||||||
filePath = item.mediaService.tempPath;
|
filePath = item.mediaService.tempPath;
|
||||||
|
if (!filePath.existsSync()) {
|
||||||
|
filePath = item.mediaService.thumbnailPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final isVideo =
|
final isVideo =
|
||||||
|
|
@ -335,13 +339,23 @@ class _SynchronizedImageViewerScreenState
|
||||||
return childWidget!;
|
return childWidget!;
|
||||||
},
|
},
|
||||||
child: !filePath.existsSync()
|
child: !filePath.existsSync()
|
||||||
? const Center(
|
? item.mediaService.mediaFile.blurhash != null
|
||||||
child: Icon(
|
? BlurHash(
|
||||||
Icons.broken_image_outlined,
|
hash: item
|
||||||
color: Colors.white38,
|
.mediaService
|
||||||
size: 64,
|
.mediaFile
|
||||||
),
|
.blurhash!,
|
||||||
)
|
optimizationMode:
|
||||||
|
BlurHashOptimizationMode
|
||||||
|
.approximation,
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.broken_image_outlined,
|
||||||
|
color: Colors.white38,
|
||||||
|
size: 64,
|
||||||
|
),
|
||||||
|
)
|
||||||
: isVideo
|
: isVideo
|
||||||
? VideoPlayerFileHelper(videoPath: filePath)
|
? VideoPlayerFileHelper(videoPath: filePath)
|
||||||
: PhotoView(
|
: PhotoView(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/model/json/backup.model.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/services/backup.service.dart';
|
import 'package:twonly/src/services/backup.service.dart';
|
||||||
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
import 'package:twonly/src/visual/elements/my_button.element.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/components/status.passwordless_recovery.comp.dart';
|
||||||
|
|
@ -21,6 +24,7 @@ class BackupView extends StatefulWidget {
|
||||||
class _BackupViewState extends State<BackupView> {
|
class _BackupViewState extends State<BackupView> {
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
CurrentBackupStatus? _backupStatus;
|
CurrentBackupStatus? _backupStatus;
|
||||||
|
server.Response_MemoriesUsage? _memoriesUsage;
|
||||||
StreamSubscription<void>? _backupUpdateSub;
|
StreamSubscription<void>? _backupUpdateSub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -41,9 +45,11 @@ class _BackupViewState extends State<BackupView> {
|
||||||
Future<void> _loadBackupStatus() async {
|
Future<void> _loadBackupStatus() async {
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
final status = await BackupService.getData();
|
final status = await BackupService.getData();
|
||||||
|
final memoriesUsage = await apiService.getMemoriesUsage();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_backupStatus = status;
|
_backupStatus = status;
|
||||||
|
_memoriesUsage = memoriesUsage;
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +188,58 @@ class _BackupViewState extends State<BackupView> {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Center(
|
||||||
|
child: Text(
|
||||||
|
'Memories Backup',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Table(
|
||||||
|
defaultVerticalAlignment:
|
||||||
|
TableCellVerticalAlignment.middle,
|
||||||
|
children: _buildTableRows([
|
||||||
|
(
|
||||||
|
'Usage',
|
||||||
|
_memoriesUsage != null
|
||||||
|
? '${formatBytes(_memoriesUsage!.currentBytes.toInt())} / ${formatBytes(_memoriesUsage!.maxBytes.toInt())}'
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Files Backed Up',
|
||||||
|
_memoriesUsage != null
|
||||||
|
? '${_memoriesUsage!.count}'
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
StreamBuilder<MemoriesBackupProgress>(
|
||||||
|
initialData: memoriesCloudService.currentProgress,
|
||||||
|
stream: memoriesCloudService.progressStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final progress = snapshot.data;
|
||||||
|
if (progress == null || progress.totalPending == 0) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final percent = (progress.currentUploaded / progress.totalPending) +
|
||||||
|
(progress.currentUploadProgress / progress.totalPending);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
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.clamp(0.0, 1.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
hasCropAnalyzed: false,
|
hasCropAnalyzed: false,
|
||||||
hasThumbnail: false,
|
hasThumbnail: false,
|
||||||
|
cloudState: CloudState.none,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
);
|
);
|
||||||
final mediaService = MediaFileService(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
|
|
|
||||||
112
memories.md
Normal file
112
memories.md
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
# Memories API Documentation
|
||||||
|
|
||||||
|
The **Memories** feature allows users to back up their media to the server in an end-to-end encrypted format. The client-server communication happens primarily via the existing WebSocket connection using Protocol Buffers (`client_to_server.proto` and `server_to_client.proto`), while the actual binary file uploads/downloads use Amazon S3 presigned URLs via HTTP PUT/GET.
|
||||||
|
|
||||||
|
## 1. Checking Storage Quota
|
||||||
|
Before allowing a user to upload a memory, the client should query the user's current storage usage to ensure they haven't exceeded their limit.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```proto
|
||||||
|
// client_to_server.proto
|
||||||
|
message GetMemoriesUsage {}
|
||||||
|
```
|
||||||
|
**Response:**
|
||||||
|
```proto
|
||||||
|
// server_to_client.proto
|
||||||
|
message MemoriesUsage {
|
||||||
|
int64 max_bytes = 1;
|
||||||
|
int64 current_bytes = 2;
|
||||||
|
int64 count = 3;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
If `current_bytes` + the new file size > `max_bytes`, the client should block the upload and optionally prompt the user to upgrade their plan or free up space.
|
||||||
|
|
||||||
|
## 2. Uploading a Memory
|
||||||
|
Uploading a memory is a three-step process: requesting presigned URLs, performing the actual HTTP uploads, and finally confirming the upload.
|
||||||
|
|
||||||
|
### Step 2.1: Request Upload URLs
|
||||||
|
The client requests permission to upload a new memory item, declaring its size and original capture date.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```proto
|
||||||
|
// client_to_server.proto
|
||||||
|
message RequestMemoriesUpload {
|
||||||
|
int64 size = 1; // Size of the FULL media file in bytes
|
||||||
|
int64 original_date = 2; // Unix timestamp (in milliseconds/seconds depending on your app's standard) of when the media was originally created
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```proto
|
||||||
|
// server_to_client.proto
|
||||||
|
message MemoriesUploadUrls {
|
||||||
|
string media_id = 1; // Unique identifier generated by the server for this memory
|
||||||
|
string thumbnail_upload_url = 2; // S3 presigned URL for uploading the encrypted thumbnail
|
||||||
|
string full_upload_url = 3; // S3 presigned URL for uploading the encrypted full media
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.2: Perform HTTP Uploads
|
||||||
|
Using the URLs returned in Step 2.1, the client encrypts the thumbnail and the full media locally, and performs an HTTP `PUT` request to the respective URLs.
|
||||||
|
|
||||||
|
*Note: The upload URLs are usually valid only for a short time (e.g. 15-60 minutes). Ensure you upload the data promptly.*
|
||||||
|
|
||||||
|
### Step 2.3: Confirm the Upload
|
||||||
|
After both the thumbnail and the full media are successfully uploaded via HTTP, the client **must** notify the server so it can mark the memory as fully uploaded and available.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```proto
|
||||||
|
// client_to_server.proto
|
||||||
|
message ConfirmMemoriesUpload {
|
||||||
|
string media_id = 1; // The media_id obtained in Step 2.1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*(The server will respond with an empty `Ok::None(true)` acknowledgment upon success).*
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Retrieving Memories (Pagination)
|
||||||
|
To display a gallery or timeline of backed-up memories, the client requests a paginated list of media items. The endpoint returns a descending list (newest first) based on the original date.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```proto
|
||||||
|
// client_to_server.proto
|
||||||
|
message GetMemoriesList {
|
||||||
|
int64 offset_date = 1; // Unix timestamp to fetch memories older than this date (for pagination). Use MAX_INT64 for the first page.
|
||||||
|
int64 limit = 2; // Maximum number of items to return in this batch (e.g. 50)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```proto
|
||||||
|
// server_to_client.proto
|
||||||
|
message MemoriesList {
|
||||||
|
repeated MediaItem items = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MediaItem {
|
||||||
|
string media_id = 1;
|
||||||
|
int64 original_date = 2;
|
||||||
|
string thumbnail_download_url = 3; // S3 presigned URL to download the encrypted thumbnail
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The client can immediately use the `thumbnail_download_url` to fetch and decrypt the thumbnail for the gallery view.
|
||||||
|
|
||||||
|
## 4. Downloading Full Media
|
||||||
|
When a user taps on a memory to view it in full screen, the client requests a temporary download URL for the full-resolution file.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```proto
|
||||||
|
// client_to_server.proto
|
||||||
|
message GetMemoriesUrl {
|
||||||
|
string media_id = 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```proto
|
||||||
|
// server_to_client.proto
|
||||||
|
message MemoriesUrl {
|
||||||
|
string full_download_url = 1; // S3 presigned URL to download the encrypted full media
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The client can then perform an HTTP `GET` request to `full_download_url`, decrypt the payload locally, and display the full-resolution image or video.
|
||||||
|
|
@ -112,6 +112,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.5.4"
|
version: "9.5.4"
|
||||||
|
blurhash_dart:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: blurhash_dart
|
||||||
|
sha256: "43955b6c2e30a7d440028d1af0fa185852f3534b795cc6eb81fbf397b464409f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ dependencies:
|
||||||
rust_lib_twonly:
|
rust_lib_twonly:
|
||||||
path: rust_builder
|
path: rust_builder
|
||||||
flutter_rust_bridge: 2.12.0
|
flutter_rust_bridge: 2.12.0
|
||||||
|
blurhash_dart: ^1.2.1
|
||||||
|
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
|
|
|
||||||
|
|
@ -124,5 +124,21 @@ impl RustKeyManager {
|
||||||
*ctx.key_manager.lock().await = key_manager;
|
*ctx.key_manager.lock().await = key_manager;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn encrypt_cloud_media_key(media_key: Vec<u8>, addition: String) -> Result<Vec<u8>> {
|
||||||
|
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||||
|
if media_key.len() != 32 {
|
||||||
|
return Err(TwonlyError::WronKeySize(32, media_key.len()));
|
||||||
|
}
|
||||||
|
let mut key_array = [0u8; 32];
|
||||||
|
key_array.copy_from_slice(&media_key);
|
||||||
|
Ok(key_manager.main_key.encrypt_cloud_media_key(&key_array, &addition))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn decrypt_cloud_media_key(encrypted_media_key: Vec<u8>, addition: String) -> Result<Vec<u8>> {
|
||||||
|
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||||
|
let decrypted = key_manager.main_key.decrypt_cloud_media_key(&encrypted_media_key, &addition)?;
|
||||||
|
Ok(decrypted.to_vec())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||||
);
|
);
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 340781866;
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1788847092;
|
||||||
|
|
||||||
// Section: executor
|
// Section: executor
|
||||||
|
|
||||||
|
|
@ -380,6 +380,38 @@ let api_password = <String>::sse_decode(&mut deserializer);deserializer.end(); m
|
||||||
})().await)
|
})().await)
|
||||||
} })
|
} })
|
||||||
}
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "rust_key_manager_decrypt_cloud_media_key", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
|
||||||
|
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
|
||||||
|
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_encrypted_media_key = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
|
let api_addition = <String>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
|
||||||
|
let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::decrypt_cloud_media_key(api_encrypted_media_key, api_addition).await?; Ok(output_ok)
|
||||||
|
})().await)
|
||||||
|
} })
|
||||||
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "rust_key_manager_encrypt_cloud_media_key", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
|
||||||
|
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
|
||||||
|
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_media_key = <Vec<u8>>::sse_decode(&mut deserializer);
|
||||||
|
let api_addition = <String>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
|
||||||
|
let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::encrypt_cloud_media_key(api_media_key, api_addition).await?; Ok(output_ok)
|
||||||
|
})().await)
|
||||||
|
} })
|
||||||
|
}
|
||||||
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -1866,36 +1898,38 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||||
15 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
15 => wire__crate__bridge__wrapper__backup__rust_backup_identity_import_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
16 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len),
|
16 => wire__crate__bridge__wrapper__backup__rust_backup_identity_restore_identity_backup_impl(port, ptr, rust_vec_len, data_len),
|
||||||
17 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
17 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
18 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len),
|
18 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_decrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
19 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
19 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_encrypt_cloud_media_key_impl(port, ptr, rust_vec_len, data_len),
|
||||||
20 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len),
|
20 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len),
|
||||||
21 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len),
|
21 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
||||||
22 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
22 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
23 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
23 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_serialized_impl(port, ptr, rust_vec_len, data_len),
|
||||||
24 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
24 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
||||||
25 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len),
|
25 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
26 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
26 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
27 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len),
|
27 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len),
|
||||||
28 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len),
|
28 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
29 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
29 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len),
|
||||||
30 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
|
30 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
31 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
|
31 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
32 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_announced_user_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
32 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||||
33 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_config_impl(port, ptr, rust_vec_len, data_len),
|
33 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
|
||||||
34 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_promotion_impl(port, ptr, rust_vec_len, data_len),
|
34 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_announced_user_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
35 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
35 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_config_impl(port, ptr, rust_vec_len, data_len),
|
||||||
36 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_other_promotions_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
36 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_promotion_impl(port, ptr, rust_vec_len, data_len),
|
||||||
37 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_own_promotions_after_version_impl(port, ptr, rust_vec_len, data_len),
|
37 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
||||||
38 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_share_for_contact_impl(port, ptr, rust_vec_len, data_len),
|
38 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_other_promotions_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
39 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_new_user_relation_impl(port, ptr, rust_vec_len, data_len),
|
39 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_own_promotions_after_version_impl(port, ptr, rust_vec_len, data_len),
|
||||||
40 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_own_promotion_and_clear_old_version_impl(port, ptr, rust_vec_len, data_len),
|
40 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_share_for_contact_impl(port, ptr, rust_vec_len, data_len),
|
||||||
41 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
41 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_new_user_relation_impl(port, ptr, rust_vec_len, data_len),
|
||||||
42 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_shares_impl(port, ptr, rust_vec_len, data_len),
|
42 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_own_promotion_and_clear_old_version_impl(port, ptr, rust_vec_len, data_len),
|
||||||
43 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_store_other_promotion_impl(port, ptr, rust_vec_len, data_len),
|
43 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
||||||
44 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_update_config_impl(port, ptr, rust_vec_len, data_len),
|
44 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||||
45 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_sign_data_impl(port, ptr, rust_vec_len, data_len),
|
45 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_store_other_promotion_impl(port, ptr, rust_vec_len, data_len),
|
||||||
46 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_signature_impl(port, ptr, rust_vec_len, data_len),
|
46 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_update_config_impl(port, ptr, rust_vec_len, data_len),
|
||||||
47 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_stored_pubkey_impl(port, ptr, rust_vec_len, data_len),
|
47 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_sign_data_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
48 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_signature_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
49 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_stored_pubkey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,23 +61,27 @@ impl MainKey {
|
||||||
self.decrypt_with_info(b"backup_key", encrypted_backup)
|
self.decrypt_with_info(b"backup_key", encrypted_backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypts a newly generated media key using the derived Media Main Key.
|
pub fn encrypt_cloud_media_key(&self, media_key: &[u8; 32], addition: &str) -> Vec<u8> {
|
||||||
// pub fn encrypt_media_key(&self, media_key: &[u8; 32]) -> Vec<u8> {
|
let info = format!("cloud_media_key_{}", addition);
|
||||||
// self.encrypt_with_info(b"media_main_key", media_key)
|
self.encrypt_with_info(info.as_bytes(), media_key)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Decrypts a wrapped media key using the derived Media Main Key.
|
pub fn decrypt_cloud_media_key(
|
||||||
// pub fn decrypt_media_key(&self, wrapped_media_key: &[u8]) -> Result<[u8; 32]> {
|
&self,
|
||||||
// let decrypted = self.decrypt_with_info(b"media_main_key", wrapped_media_key)?;
|
encrypted_media_key: &[u8],
|
||||||
|
addition: &str,
|
||||||
|
) -> Result<[u8; 32]> {
|
||||||
|
let info = format!("cloud_media_key_{}", addition);
|
||||||
|
let decrypted = self.decrypt_with_info(info.as_bytes(), encrypted_media_key)?;
|
||||||
|
|
||||||
// if decrypted.len() != 32 {
|
if decrypted.len() != 32 {
|
||||||
// return Err("Invalid decrypted key length".to_string())?;
|
return Err("Invalid decrypted key length".to_string())?;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let mut result = [0u8; 32];
|
let mut result = [0u8; 32];
|
||||||
// result.copy_from_slice(&decrypted);
|
result.copy_from_slice(&decrypted);
|
||||||
// Ok(result)
|
Ok(result)
|
||||||
// }
|
}
|
||||||
|
|
||||||
fn derive_key(&self, info: &[u8]) -> [u8; 32] {
|
fn derive_key(&self, info: &[u8]) -> [u8; 32] {
|
||||||
let hk = Hkdf::<Sha256>::new(None, &self.main_key);
|
let hk = Hkdf::<Sha256>::new(None, &self.main_key);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import 'schema_v19.dart' as v19;
|
||||||
import 'schema_v20.dart' as v20;
|
import 'schema_v20.dart' as v20;
|
||||||
import 'schema_v21.dart' as v21;
|
import 'schema_v21.dart' as v21;
|
||||||
import 'schema_v22.dart' as v22;
|
import 'schema_v22.dart' as v22;
|
||||||
|
import 'schema_v23.dart' as v23;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
|
|
@ -75,6 +76,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
return v21.DatabaseAtV21(db);
|
return v21.DatabaseAtV21(db);
|
||||||
case 22:
|
case 22:
|
||||||
return v22.DatabaseAtV22(db);
|
return v22.DatabaseAtV22(db);
|
||||||
|
case 23:
|
||||||
|
return v23.DatabaseAtV23(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
|
|
@ -103,5 +106,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
20,
|
20,
|
||||||
21,
|
21,
|
||||||
22,
|
22,
|
||||||
|
23,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11135
test/drift/twonly_db/generated/schema_v23.dart
Normal file
11135
test/drift/twonly_db/generated/schema_v23.dart
Normal file
File diff suppressed because it is too large
Load diff
151
test/services/cloud_backup_test.dart
Normal file
151
test/services/cloud_backup_test.dart
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:twonly/core/bridge.dart' as bridge;
|
||||||
|
import 'package:twonly/core/frb_generated.dart';
|
||||||
|
import 'package:twonly/globals.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/services/api/api.service.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/user.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
import '../mocks/platform_channels.dart';
|
||||||
|
import '../mocks/test_client.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Memories Cloud Backup Integration', () {
|
||||||
|
late TestClient client;
|
||||||
|
late Directory tempDir;
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
|
.setMockMethodCallHandler(
|
||||||
|
const MethodChannel('dev.fluttercommunity.plus/package_info'),
|
||||||
|
(call) async {
|
||||||
|
return {
|
||||||
|
'appName': 'twonly',
|
||||||
|
'packageName': 'eu.twonly.app',
|
||||||
|
'version': '1.0.0',
|
||||||
|
'buildNumber': '100',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Log.init();
|
||||||
|
setupPlatformChannelMocks();
|
||||||
|
HttpOverrides.global = RealHttpOverrides();
|
||||||
|
final dylibPath =
|
||||||
|
'${Directory.current.path}/rust/target/debug/librust_lib_twonly.dylib';
|
||||||
|
if (File(dylibPath).existsSync()) {
|
||||||
|
await RustLib.init(externalLibrary: ExternalLibrary.open(dylibPath));
|
||||||
|
} else {
|
||||||
|
await RustLib.init();
|
||||||
|
}
|
||||||
|
tempDir = Directory.systemTemp.createTempSync(
|
||||||
|
'twonly_cloud_backup_test_',
|
||||||
|
);
|
||||||
|
AppEnvironment.initTesting(
|
||||||
|
customCacheDir: tempDir.path,
|
||||||
|
customSupportDir: tempDir.path,
|
||||||
|
);
|
||||||
|
|
||||||
|
await bridge.initializeTwonlyFlutter(
|
||||||
|
config: bridge.InitConfig(
|
||||||
|
databaseDir: tempDir.path,
|
||||||
|
dataDir: tempDir.path,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (locator.isRegistered<TwonlyDB>()) {
|
||||||
|
await locator.unregister<TwonlyDB>();
|
||||||
|
}
|
||||||
|
if (locator.isRegistered<UserService>()) {
|
||||||
|
await locator.unregister<UserService>();
|
||||||
|
}
|
||||||
|
if (locator.isRegistered<ApiService>()) {
|
||||||
|
await locator.unregister<ApiService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
locator
|
||||||
|
..registerFactory<TwonlyDB>(() {
|
||||||
|
final db = Zone.current[#twonlyDB] as TwonlyDB?;
|
||||||
|
if (db != null) return db;
|
||||||
|
throw StateError('No TwonlyDB in active Zone.');
|
||||||
|
})
|
||||||
|
..registerFactory<UserService>(() {
|
||||||
|
final us = Zone.current[#userService] as UserService?;
|
||||||
|
if (us != null) return us;
|
||||||
|
throw StateError('No UserService in active Zone.');
|
||||||
|
})
|
||||||
|
..registerFactory<ApiService>(() {
|
||||||
|
final api = Zone.current[#apiService] as ApiService?;
|
||||||
|
if (api != null) return api;
|
||||||
|
throw StateError('No ApiService in active Zone.');
|
||||||
|
});
|
||||||
|
|
||||||
|
client = TestClient(777);
|
||||||
|
await client.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
try {
|
||||||
|
if (tempDir.existsSync()) {
|
||||||
|
tempDir.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Full Backup Lifecycle', () async {
|
||||||
|
await client.run(() async {
|
||||||
|
// 1. Create a dummy media file
|
||||||
|
final mediaId = 'test_media_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
|
await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
|
||||||
|
MediaFilesCompanion(
|
||||||
|
mediaId: Value(mediaId),
|
||||||
|
createdAt: Value(DateTime.now()),
|
||||||
|
stored: const Value(true),
|
||||||
|
hasThumbnail: const Value(true),
|
||||||
|
cloudState: const Value(CloudState.none),
|
||||||
|
type: const Value(MediaType.image),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
|
mediaId,
|
||||||
|
);
|
||||||
|
expect(mediaFile, isNotNull);
|
||||||
|
expect(mediaFile!.cloudState, CloudState.none);
|
||||||
|
|
||||||
|
// Mock original and thumbnail files
|
||||||
|
final mediaService = MediaFileService(mediaFile);
|
||||||
|
final originalFile = mediaService.originalPath;
|
||||||
|
final thumbnailFile = mediaService.thumbnailPath;
|
||||||
|
|
||||||
|
await originalFile.create(recursive: true);
|
||||||
|
await originalFile.writeAsBytes([1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
|
await thumbnailFile.create(recursive: true);
|
||||||
|
await thumbnailFile.writeAsBytes([1, 2, 3]);
|
||||||
|
|
||||||
|
// 2. Trigger checkUploads (which performs S3 POST upload and confirmation)
|
||||||
|
await memoriesCloudService.checkUploads();
|
||||||
|
|
||||||
|
// 3. Verify that the media has cloudState == uploaded
|
||||||
|
final finalMedia = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
|
mediaId,
|
||||||
|
);
|
||||||
|
expect(finalMedia, isNotNull);
|
||||||
|
expect(finalMedia!.cloudState, CloudState.uploaded);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue