mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
Merge pull request #309 from twonlyapp/dev
- Option to export and import memories - iOS support for ultra-wide-angle camera - Support Android Monochrome Icon - Multiple layout issues fixed - Multiple bug fixes
This commit is contained in:
commit
39c8d4990f
76 changed files with 22102 additions and 332 deletions
|
|
@ -1,5 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## 0.0.69
|
||||
|
||||
- Option to export and import memories
|
||||
- iOS support for ultra-wide-angle camera
|
||||
- Support Android Monochrome Icon
|
||||
- Multiple layout issues fixed
|
||||
- Multiple bug fixes
|
||||
|
||||
## 0.0.67
|
||||
|
||||
- Adds crash reports (optional). Please consider enabling this under Settings > Help > “Share errors and crashes with us.”
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -6,4 +6,9 @@
|
|||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
</foreground>
|
||||
<monochrome>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_monochrome"
|
||||
android:inset="16%" />
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ targets:
|
|||
options:
|
||||
databases:
|
||||
twonly_db: lib/src/database/twonly.db.dart
|
||||
twonly_database: lib/src/database/twonly_database_old.dart
|
||||
twonly_database: lib/src/database/twonly_database_old.dart
|
||||
schema_dir: lib/src/database/schemas
|
||||
|
|
@ -11,6 +11,37 @@ PODS:
|
|||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.9):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.9)
|
||||
- DKImagePickerController/PhotoGallery (4.3.9):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.9)
|
||||
- DKPhotoGallery (0.0.19):
|
||||
- DKPhotoGallery/Core (= 0.0.19)
|
||||
- DKPhotoGallery/Model (= 0.0.19)
|
||||
- DKPhotoGallery/Preview (= 0.0.19)
|
||||
- DKPhotoGallery/Resource (= 0.0.19)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.19):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- emoji_picker_flutter (0.0.1):
|
||||
- Flutter
|
||||
- ffmpeg_kit_flutter_new (1.0.0):
|
||||
|
|
@ -18,6 +49,9 @@ PODS:
|
|||
- Flutter
|
||||
- ffmpeg_kit_flutter_new/full-gpl (1.0.0):
|
||||
- Flutter
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase (12.4.0):
|
||||
- Firebase/Core (= 12.4.0)
|
||||
- Firebase/Core (12.4.0):
|
||||
|
|
@ -249,6 +283,7 @@ PODS:
|
|||
- sqlite3/rtree
|
||||
- sqlite3/session
|
||||
- SwiftProtobuf (1.33.1)
|
||||
- SwiftyGif (5.4.5)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
|
|
@ -264,6 +299,7 @@ DEPENDENCIES:
|
|||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
||||
- ffmpeg_kit_flutter_new (from `.symlinks/plugins/ffmpeg_kit_flutter_new/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Firebase
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
|
|
@ -298,6 +334,8 @@ DEPENDENCIES:
|
|||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- Firebase
|
||||
- FirebaseAnalytics
|
||||
- FirebaseCore
|
||||
|
|
@ -318,6 +356,7 @@ SPEC REPOS:
|
|||
- Sentry
|
||||
- sqlite3
|
||||
- SwiftProtobuf
|
||||
- SwiftyGif
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
audio_waveforms:
|
||||
|
|
@ -336,6 +375,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
||||
ffmpeg_kit_flutter_new:
|
||||
:path: ".symlinks/plugins/ffmpeg_kit_flutter_new/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
firebase_core:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_messaging:
|
||||
|
|
@ -394,8 +435,11 @@ SPEC CHECKSUMS:
|
|||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
||||
ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
||||
firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594
|
||||
firebase_messaging: c17a29984eafce4b2997fe078bb0a9e0b06f5dde
|
||||
|
|
@ -439,6 +483,7 @@ SPEC CHECKSUMS:
|
|||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
SwiftProtobuf: 533a18409c3ca3a6156b2b1e46afd0f69e751aba
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
|
@ -27,29 +29,7 @@ import 'package:twonly/src/utils/log.dart';
|
|||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
try {
|
||||
await initFCMService();
|
||||
} catch (e) {
|
||||
Log.error('$e');
|
||||
}
|
||||
|
||||
initLogger();
|
||||
|
||||
final user = await getUser();
|
||||
if (user != null) {
|
||||
gUser = user;
|
||||
unawaited(performTwonlySafeBackup());
|
||||
}
|
||||
|
||||
final settingsController = SettingsChangeProvider();
|
||||
|
||||
await settingsController.loadSettings();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
unawaited(setupPushNotification());
|
||||
|
||||
gCameras = await availableCameras();
|
||||
SentryWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// try {
|
||||
// File(join((await getApplicationSupportDirectory()).path, 'twonly.sqlite'))
|
||||
|
|
@ -60,44 +40,67 @@ void main() async {
|
|||
// return u;
|
||||
// });
|
||||
|
||||
apiService = ApiService();
|
||||
twonlyDB = TwonlyDB();
|
||||
|
||||
await initFileDownloader();
|
||||
unawaited(finishStartedPreprocessing());
|
||||
|
||||
unawaited(MediaFileService.purgeTempFolder());
|
||||
unawaited(createPushAvatars());
|
||||
await twonlyDB.messagesDao.purgeMessageTable();
|
||||
|
||||
final providers = [
|
||||
ChangeNotifierProvider(create: (_) => settingsController),
|
||||
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||
];
|
||||
|
||||
final user = await getUser();
|
||||
if (user != null) {
|
||||
gUser = user;
|
||||
|
||||
if (user.allowErrorTrackingViaSentry) {
|
||||
globalAllowErrorTrackingViaSentry = true;
|
||||
return SentryFlutter.init(
|
||||
await SentryFlutter.init(
|
||||
(options) => options
|
||||
..dsn =
|
||||
'https://6b24a012c85144c9b522440a1d17d01c@glitchtip.twonly.eu/4'
|
||||
..tracesSampleRate = 0.01
|
||||
..tracesSampleRate = 0.1
|
||||
..enableAutoSessionTracking = false,
|
||||
appRunner: () => runApp(
|
||||
MultiProvider(
|
||||
providers: providers,
|
||||
child: const App(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
unawaited(performTwonlySafeBackup());
|
||||
}
|
||||
|
||||
await initFCMService();
|
||||
|
||||
initLogger();
|
||||
|
||||
final settingsController = SettingsChangeProvider();
|
||||
|
||||
await settingsController.loadSettings();
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp],
|
||||
);
|
||||
|
||||
unawaited(setupPushNotification());
|
||||
|
||||
gCameras = await availableCameras();
|
||||
|
||||
apiService = ApiService();
|
||||
twonlyDB = TwonlyDB();
|
||||
|
||||
await twonlyDB.messagesDao.purgeMessageTable();
|
||||
unawaited(MediaFileService.purgeTempFolder());
|
||||
|
||||
await initFileDownloader();
|
||||
if (Platform.isAndroid) {
|
||||
if ((await DeviceInfoPlugin().androidInfo).version.release == '9') {
|
||||
Future.delayed(const Duration(seconds: 20), () {
|
||||
unawaited(finishStartedPreprocessing());
|
||||
});
|
||||
} else {
|
||||
unawaited(finishStartedPreprocessing());
|
||||
}
|
||||
} else {
|
||||
unawaited(finishStartedPreprocessing());
|
||||
}
|
||||
|
||||
unawaited(createPushAvatars());
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: providers,
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => settingsController),
|
||||
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||
],
|
||||
child: const App(),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -247,16 +247,22 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
.get();
|
||||
}
|
||||
|
||||
Future<List<GroupMember>> getAllGroupMemberWithoutPublicKey() {
|
||||
final query =
|
||||
((select(groups)..where((t) => t.isDirectChat.equals(false))).join([
|
||||
leftOuterJoin(
|
||||
groupMembers,
|
||||
groupMembers.groupId.equalsExp(groups.groupId),
|
||||
),
|
||||
])
|
||||
..where(groupMembers.groupPublicKey.isNull()));
|
||||
return query.map((row) => row.readTable(groupMembers)).get();
|
||||
Future<List<GroupMember>> getAllGroupMemberWithoutPublicKey() async {
|
||||
try {
|
||||
final query = ((select(groupMembers)
|
||||
..where((t) => t.groupPublicKey.isNull()))
|
||||
.join([
|
||||
leftOuterJoin(
|
||||
groups,
|
||||
groups.groupId.equalsExp(groupMembers.groupId),
|
||||
),
|
||||
])
|
||||
..where(groups.isDirectChat.isNull()));
|
||||
return query.map((row) => row.readTable(groupMembers)).get();
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<Group?> getDirectChat(int userId) async {
|
||||
|
|
|
|||
|
|
@ -119,11 +119,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
..where(
|
||||
(m) =>
|
||||
m.groupId.equals(group.groupId) &
|
||||
// m.messageId.equals(lastMessage.messageId).not() &
|
||||
(m.mediaStored.equals(true) &
|
||||
m.isDeletedFromSender.equals(true) |
|
||||
m.mediaStored.equals(false)) &
|
||||
(m.openedByAll.isSmallerThanValue(deletionTime) |
|
||||
(m.openedAt.isSmallerThanValue(deletionTime) |
|
||||
(m.isDeletedFromSender.equals(true) &
|
||||
m.createdAt.isSmallerThanValue(deletionTime))),
|
||||
))
|
||||
|
|
|
|||
1
lib/src/database/schemas/twonly_db/drift_schema_v1.json
Normal file
1
lib/src/database/schemas/twonly_db/drift_schema_v1.json
Normal file
File diff suppressed because one or more lines are too long
1
lib/src/database/schemas/twonly_db/drift_schema_v2.json
Normal file
1
lib/src/database/schemas/twonly_db/drift_schema_v2.json
Normal file
File diff suppressed because one or more lines are too long
1
lib/src/database/schemas/twonly_db/drift_schema_v3.json
Normal file
1
lib/src/database/schemas/twonly_db/drift_schema_v3.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -20,7 +20,7 @@ class ConnectPreKeyStore extends PreKeyStore {
|
|||
.get();
|
||||
if (preKeyRecord.isEmpty) {
|
||||
throw InvalidKeyIdException(
|
||||
'[PREKEY] No such preKey record! - $preKeyId',
|
||||
'[PREKEY] No such preKey record!',
|
||||
);
|
||||
}
|
||||
final preKey = preKeyRecord.first.preKey;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class Groups extends Table {
|
|||
BlobColumn get myGroupPrivateKey => blob().nullable()();
|
||||
|
||||
TextColumn get groupName => text()();
|
||||
TextColumn get draftMessage => text().nullable()();
|
||||
|
||||
IntColumn get totalMediaCounter => integer().withDefault(const Constant(0))();
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,6 @@ class MediaFiles extends Table {
|
|||
BoolColumn get requiresAuthentication =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
BoolColumn get reopenByContact =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
BoolColumn get stored => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get isDraftMedia => boolean().withDefault(const Constant(false))();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ class Messages extends Table {
|
|||
.references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)();
|
||||
|
||||
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get mediaReopened =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
BlobColumn get downloadToken => blob().nullable()();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import 'package:twonly/src/database/tables/signal_identity_key_store.table.dart'
|
|||
import 'package:twonly/src/database/tables/signal_pre_key_store.table.dart';
|
||||
import 'package:twonly/src/database/tables/signal_sender_key_store.table.dart';
|
||||
import 'package:twonly/src/database/tables/signal_session_store.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.steps.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
part 'twonly.db.g.dart';
|
||||
|
|
@ -66,7 +67,7 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
int get schemaVersion => 3;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
|
|
@ -83,7 +84,15 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
beforeOpen: (details) async {
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
},
|
||||
// onUpgrade: stepByStep(),
|
||||
onUpgrade: stepByStep(
|
||||
from1To2: (m, schema) async {
|
||||
await m.addColumn(schema.messages, schema.messages.mediaReopened);
|
||||
await m.dropColumn(schema.mediaFiles, 'reopen_by_contact');
|
||||
},
|
||||
from2To3: (m, schema) async {
|
||||
await m.addColumn(schema.groups, schema.groups.draftMessage);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -105,25 +114,39 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
}
|
||||
|
||||
Future<void> deleteDataForTwonlySafe() async {
|
||||
// await delete(messages).go();
|
||||
// await delete(messageRetransmissions).go();
|
||||
// await delete(mediaUploads).go();
|
||||
// await update(contacts).write(
|
||||
// const ContactsCompanion(
|
||||
// avatarSvg: Value(null),
|
||||
// myAvatarCounter: Value(0),
|
||||
// ),
|
||||
// );
|
||||
// await delete(signalContactPreKeys).go();
|
||||
// await delete(signalContactSignedPreKeys).go();
|
||||
// await (delete(signalPreKeyStores)
|
||||
// ..where(
|
||||
// (t) => (t.createdAt.isSmallerThanValue(
|
||||
// DateTime.now().subtract(
|
||||
// const Duration(days: 25),
|
||||
// ),
|
||||
// )),
|
||||
// ))
|
||||
// .go();
|
||||
await (delete(messages)
|
||||
..where(
|
||||
(t) => (t.mediaStored.equals(false) &
|
||||
t.isDeletedFromSender.equals(false)),
|
||||
))
|
||||
.go();
|
||||
await update(messages).write(
|
||||
const MessagesCompanion(
|
||||
downloadToken: Value(null),
|
||||
),
|
||||
);
|
||||
await (delete(mediaFiles)
|
||||
..where(
|
||||
(t) => (t.stored.equals(false)),
|
||||
))
|
||||
.go();
|
||||
await delete(receipts).go();
|
||||
await update(contacts).write(
|
||||
const ContactsCompanion(
|
||||
avatarSvgCompressed: Value(null),
|
||||
senderProfileCounter: Value(0),
|
||||
),
|
||||
);
|
||||
await delete(signalContactPreKeys).go();
|
||||
await delete(signalContactSignedPreKeys).go();
|
||||
await (delete(signalPreKeyStores)
|
||||
..where(
|
||||
(t) => (t.createdAt.isSmallerThanValue(
|
||||
DateTime.now().subtract(
|
||||
const Duration(days: 25),
|
||||
),
|
||||
)),
|
||||
))
|
||||
.go();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -760,6 +760,12 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
|||
late final GeneratedColumn<String> groupName = GeneratedColumn<String>(
|
||||
'group_name', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
static const VerificationMeta _draftMessageMeta =
|
||||
const VerificationMeta('draftMessage');
|
||||
@override
|
||||
late final GeneratedColumn<String> draftMessage = GeneratedColumn<String>(
|
||||
'draft_message', aliasedName, true,
|
||||
type: DriftSqlType.string, requiredDuringInsert: false);
|
||||
static const VerificationMeta _totalMediaCounterMeta =
|
||||
const VerificationMeta('totalMediaCounter');
|
||||
@override
|
||||
|
|
@ -863,6 +869,7 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
|||
stateEncryptionKey,
|
||||
myGroupPrivateKey,
|
||||
groupName,
|
||||
draftMessage,
|
||||
totalMediaCounter,
|
||||
alsoBestFriend,
|
||||
deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -952,6 +959,12 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
|||
} else if (isInserting) {
|
||||
context.missing(_groupNameMeta);
|
||||
}
|
||||
if (data.containsKey('draft_message')) {
|
||||
context.handle(
|
||||
_draftMessageMeta,
|
||||
draftMessage.isAcceptableOrUnknown(
|
||||
data['draft_message']!, _draftMessageMeta));
|
||||
}
|
||||
if (data.containsKey('total_media_counter')) {
|
||||
context.handle(
|
||||
_totalMediaCounterMeta,
|
||||
|
|
@ -1056,6 +1069,8 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
|||
DriftSqlType.blob, data['${effectivePrefix}my_group_private_key']),
|
||||
groupName: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}group_name'])!,
|
||||
draftMessage: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}draft_message']),
|
||||
totalMediaCounter: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int, data['${effectivePrefix}total_media_counter'])!,
|
||||
alsoBestFriend: attachedDatabase.typeMapping
|
||||
|
|
@ -1107,6 +1122,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
final Uint8List? stateEncryptionKey;
|
||||
final Uint8List? myGroupPrivateKey;
|
||||
final String groupName;
|
||||
final String? draftMessage;
|
||||
final int totalMediaCounter;
|
||||
final bool alsoBestFriend;
|
||||
final int deleteMessagesAfterMilliseconds;
|
||||
|
|
@ -1132,6 +1148,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
this.stateEncryptionKey,
|
||||
this.myGroupPrivateKey,
|
||||
required this.groupName,
|
||||
this.draftMessage,
|
||||
required this.totalMediaCounter,
|
||||
required this.alsoBestFriend,
|
||||
required this.deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -1163,6 +1180,9 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
map['my_group_private_key'] = Variable<Uint8List>(myGroupPrivateKey);
|
||||
}
|
||||
map['group_name'] = Variable<String>(groupName);
|
||||
if (!nullToAbsent || draftMessage != null) {
|
||||
map['draft_message'] = Variable<String>(draftMessage);
|
||||
}
|
||||
map['total_media_counter'] = Variable<int>(totalMediaCounter);
|
||||
map['also_best_friend'] = Variable<bool>(alsoBestFriend);
|
||||
map['delete_messages_after_milliseconds'] =
|
||||
|
|
@ -1208,6 +1228,9 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
? const Value.absent()
|
||||
: Value(myGroupPrivateKey),
|
||||
groupName: Value(groupName),
|
||||
draftMessage: draftMessage == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(draftMessage),
|
||||
totalMediaCounter: Value(totalMediaCounter),
|
||||
alsoBestFriend: Value(alsoBestFriend),
|
||||
deleteMessagesAfterMilliseconds: Value(deleteMessagesAfterMilliseconds),
|
||||
|
|
@ -1251,6 +1274,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
myGroupPrivateKey:
|
||||
serializer.fromJson<Uint8List?>(json['myGroupPrivateKey']),
|
||||
groupName: serializer.fromJson<String>(json['groupName']),
|
||||
draftMessage: serializer.fromJson<String?>(json['draftMessage']),
|
||||
totalMediaCounter: serializer.fromJson<int>(json['totalMediaCounter']),
|
||||
alsoBestFriend: serializer.fromJson<bool>(json['alsoBestFriend']),
|
||||
deleteMessagesAfterMilliseconds:
|
||||
|
|
@ -1286,6 +1310,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
'stateEncryptionKey': serializer.toJson<Uint8List?>(stateEncryptionKey),
|
||||
'myGroupPrivateKey': serializer.toJson<Uint8List?>(myGroupPrivateKey),
|
||||
'groupName': serializer.toJson<String>(groupName),
|
||||
'draftMessage': serializer.toJson<String?>(draftMessage),
|
||||
'totalMediaCounter': serializer.toJson<int>(totalMediaCounter),
|
||||
'alsoBestFriend': serializer.toJson<bool>(alsoBestFriend),
|
||||
'deleteMessagesAfterMilliseconds':
|
||||
|
|
@ -1316,6 +1341,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
Value<Uint8List?> stateEncryptionKey = const Value.absent(),
|
||||
Value<Uint8List?> myGroupPrivateKey = const Value.absent(),
|
||||
String? groupName,
|
||||
Value<String?> draftMessage = const Value.absent(),
|
||||
int? totalMediaCounter,
|
||||
bool? alsoBestFriend,
|
||||
int? deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -1345,6 +1371,8 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
? myGroupPrivateKey.value
|
||||
: this.myGroupPrivateKey,
|
||||
groupName: groupName ?? this.groupName,
|
||||
draftMessage:
|
||||
draftMessage.present ? draftMessage.value : this.draftMessage,
|
||||
totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter,
|
||||
alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend,
|
||||
deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds ??
|
||||
|
|
@ -1395,6 +1423,9 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
? data.myGroupPrivateKey.value
|
||||
: this.myGroupPrivateKey,
|
||||
groupName: data.groupName.present ? data.groupName.value : this.groupName,
|
||||
draftMessage: data.draftMessage.present
|
||||
? data.draftMessage.value
|
||||
: this.draftMessage,
|
||||
totalMediaCounter: data.totalMediaCounter.present
|
||||
? data.totalMediaCounter.value
|
||||
: this.totalMediaCounter,
|
||||
|
|
@ -1448,6 +1479,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
..write('stateEncryptionKey: $stateEncryptionKey, ')
|
||||
..write('myGroupPrivateKey: $myGroupPrivateKey, ')
|
||||
..write('groupName: $groupName, ')
|
||||
..write('draftMessage: $draftMessage, ')
|
||||
..write('totalMediaCounter: $totalMediaCounter, ')
|
||||
..write('alsoBestFriend: $alsoBestFriend, ')
|
||||
..write(
|
||||
|
|
@ -1479,6 +1511,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
$driftBlobEquality.hash(stateEncryptionKey),
|
||||
$driftBlobEquality.hash(myGroupPrivateKey),
|
||||
groupName,
|
||||
draftMessage,
|
||||
totalMediaCounter,
|
||||
alsoBestFriend,
|
||||
deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -1510,6 +1543,7 @@ class Group extends DataClass implements Insertable<Group> {
|
|||
$driftBlobEquality.equals(
|
||||
other.myGroupPrivateKey, this.myGroupPrivateKey) &&
|
||||
other.groupName == this.groupName &&
|
||||
other.draftMessage == this.draftMessage &&
|
||||
other.totalMediaCounter == this.totalMediaCounter &&
|
||||
other.alsoBestFriend == this.alsoBestFriend &&
|
||||
other.deleteMessagesAfterMilliseconds ==
|
||||
|
|
@ -1538,6 +1572,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
final Value<Uint8List?> stateEncryptionKey;
|
||||
final Value<Uint8List?> myGroupPrivateKey;
|
||||
final Value<String> groupName;
|
||||
final Value<String?> draftMessage;
|
||||
final Value<int> totalMediaCounter;
|
||||
final Value<bool> alsoBestFriend;
|
||||
final Value<int> deleteMessagesAfterMilliseconds;
|
||||
|
|
@ -1564,6 +1599,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
this.stateEncryptionKey = const Value.absent(),
|
||||
this.myGroupPrivateKey = const Value.absent(),
|
||||
this.groupName = const Value.absent(),
|
||||
this.draftMessage = const Value.absent(),
|
||||
this.totalMediaCounter = const Value.absent(),
|
||||
this.alsoBestFriend = const Value.absent(),
|
||||
this.deleteMessagesAfterMilliseconds = const Value.absent(),
|
||||
|
|
@ -1591,6 +1627,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
this.stateEncryptionKey = const Value.absent(),
|
||||
this.myGroupPrivateKey = const Value.absent(),
|
||||
required String groupName,
|
||||
this.draftMessage = const Value.absent(),
|
||||
this.totalMediaCounter = const Value.absent(),
|
||||
this.alsoBestFriend = const Value.absent(),
|
||||
this.deleteMessagesAfterMilliseconds = const Value.absent(),
|
||||
|
|
@ -1619,6 +1656,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
Expression<Uint8List>? stateEncryptionKey,
|
||||
Expression<Uint8List>? myGroupPrivateKey,
|
||||
Expression<String>? groupName,
|
||||
Expression<String>? draftMessage,
|
||||
Expression<int>? totalMediaCounter,
|
||||
Expression<bool>? alsoBestFriend,
|
||||
Expression<int>? deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -1647,6 +1685,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
'state_encryption_key': stateEncryptionKey,
|
||||
if (myGroupPrivateKey != null) 'my_group_private_key': myGroupPrivateKey,
|
||||
if (groupName != null) 'group_name': groupName,
|
||||
if (draftMessage != null) 'draft_message': draftMessage,
|
||||
if (totalMediaCounter != null) 'total_media_counter': totalMediaCounter,
|
||||
if (alsoBestFriend != null) 'also_best_friend': alsoBestFriend,
|
||||
if (deleteMessagesAfterMilliseconds != null)
|
||||
|
|
@ -1681,6 +1720,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
Value<Uint8List?>? stateEncryptionKey,
|
||||
Value<Uint8List?>? myGroupPrivateKey,
|
||||
Value<String>? groupName,
|
||||
Value<String?>? draftMessage,
|
||||
Value<int>? totalMediaCounter,
|
||||
Value<bool>? alsoBestFriend,
|
||||
Value<int>? deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -1707,6 +1747,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
stateEncryptionKey: stateEncryptionKey ?? this.stateEncryptionKey,
|
||||
myGroupPrivateKey: myGroupPrivateKey ?? this.myGroupPrivateKey,
|
||||
groupName: groupName ?? this.groupName,
|
||||
draftMessage: draftMessage ?? this.draftMessage,
|
||||
totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter,
|
||||
alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend,
|
||||
deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds ??
|
||||
|
|
@ -1766,6 +1807,9 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
if (groupName.present) {
|
||||
map['group_name'] = Variable<String>(groupName.value);
|
||||
}
|
||||
if (draftMessage.present) {
|
||||
map['draft_message'] = Variable<String>(draftMessage.value);
|
||||
}
|
||||
if (totalMediaCounter.present) {
|
||||
map['total_media_counter'] = Variable<int>(totalMediaCounter.value);
|
||||
}
|
||||
|
|
@ -1828,6 +1872,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
|||
..write('stateEncryptionKey: $stateEncryptionKey, ')
|
||||
..write('myGroupPrivateKey: $myGroupPrivateKey, ')
|
||||
..write('groupName: $groupName, ')
|
||||
..write('draftMessage: $draftMessage, ')
|
||||
..write('totalMediaCounter: $totalMediaCounter, ')
|
||||
..write('alsoBestFriend: $alsoBestFriend, ')
|
||||
..write(
|
||||
|
|
@ -1886,16 +1931,6 @@ class $MediaFilesTable extends MediaFiles
|
|||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("requires_authentication" IN (0, 1))'),
|
||||
defaultValue: const Constant(false));
|
||||
static const VerificationMeta _reopenByContactMeta =
|
||||
const VerificationMeta('reopenByContact');
|
||||
@override
|
||||
late final GeneratedColumn<bool> reopenByContact = GeneratedColumn<bool>(
|
||||
'reopen_by_contact', aliasedName, false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("reopen_by_contact" IN (0, 1))'),
|
||||
defaultValue: const Constant(false));
|
||||
static const VerificationMeta _storedMeta = const VerificationMeta('stored');
|
||||
@override
|
||||
late final GeneratedColumn<bool> stored = GeneratedColumn<bool>(
|
||||
|
|
@ -1976,7 +2011,6 @@ class $MediaFilesTable extends MediaFiles
|
|||
uploadState,
|
||||
downloadState,
|
||||
requiresAuthentication,
|
||||
reopenByContact,
|
||||
stored,
|
||||
isDraftMedia,
|
||||
reuploadRequestedBy,
|
||||
|
|
@ -2010,12 +2044,6 @@ class $MediaFilesTable extends MediaFiles
|
|||
requiresAuthentication.isAcceptableOrUnknown(
|
||||
data['requires_authentication']!, _requiresAuthenticationMeta));
|
||||
}
|
||||
if (data.containsKey('reopen_by_contact')) {
|
||||
context.handle(
|
||||
_reopenByContactMeta,
|
||||
reopenByContact.isAcceptableOrUnknown(
|
||||
data['reopen_by_contact']!, _reopenByContactMeta));
|
||||
}
|
||||
if (data.containsKey('stored')) {
|
||||
context.handle(_storedMeta,
|
||||
stored.isAcceptableOrUnknown(data['stored']!, _storedMeta));
|
||||
|
|
@ -2089,8 +2117,6 @@ class $MediaFilesTable extends MediaFiles
|
|||
requiresAuthentication: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}requires_authentication'])!,
|
||||
reopenByContact: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool, data['${effectivePrefix}reopen_by_contact'])!,
|
||||
stored: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}stored'])!,
|
||||
isDraftMedia: attachedDatabase.typeMapping
|
||||
|
|
@ -2146,7 +2172,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
final UploadState? uploadState;
|
||||
final DownloadState? downloadState;
|
||||
final bool requiresAuthentication;
|
||||
final bool reopenByContact;
|
||||
final bool stored;
|
||||
final bool isDraftMedia;
|
||||
final List<int>? reuploadRequestedBy;
|
||||
|
|
@ -2163,7 +2188,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
this.uploadState,
|
||||
this.downloadState,
|
||||
required this.requiresAuthentication,
|
||||
required this.reopenByContact,
|
||||
required this.stored,
|
||||
required this.isDraftMedia,
|
||||
this.reuploadRequestedBy,
|
||||
|
|
@ -2191,7 +2215,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
$MediaFilesTable.$converterdownloadStaten.toSql(downloadState));
|
||||
}
|
||||
map['requires_authentication'] = Variable<bool>(requiresAuthentication);
|
||||
map['reopen_by_contact'] = Variable<bool>(reopenByContact);
|
||||
map['stored'] = Variable<bool>(stored);
|
||||
map['is_draft_media'] = Variable<bool>(isDraftMedia);
|
||||
if (!nullToAbsent || reuploadRequestedBy != null) {
|
||||
|
|
@ -2233,7 +2256,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
? const Value.absent()
|
||||
: Value(downloadState),
|
||||
requiresAuthentication: Value(requiresAuthentication),
|
||||
reopenByContact: Value(reopenByContact),
|
||||
stored: Value(stored),
|
||||
isDraftMedia: Value(isDraftMedia),
|
||||
reuploadRequestedBy: reuploadRequestedBy == null && nullToAbsent
|
||||
|
|
@ -2275,7 +2297,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
.fromJson(serializer.fromJson<String?>(json['downloadState'])),
|
||||
requiresAuthentication:
|
||||
serializer.fromJson<bool>(json['requiresAuthentication']),
|
||||
reopenByContact: serializer.fromJson<bool>(json['reopenByContact']),
|
||||
stored: serializer.fromJson<bool>(json['stored']),
|
||||
isDraftMedia: serializer.fromJson<bool>(json['isDraftMedia']),
|
||||
reuploadRequestedBy:
|
||||
|
|
@ -2302,7 +2323,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
'downloadState': serializer.toJson<String?>(
|
||||
$MediaFilesTable.$converterdownloadStaten.toJson(downloadState)),
|
||||
'requiresAuthentication': serializer.toJson<bool>(requiresAuthentication),
|
||||
'reopenByContact': serializer.toJson<bool>(reopenByContact),
|
||||
'stored': serializer.toJson<bool>(stored),
|
||||
'isDraftMedia': serializer.toJson<bool>(isDraftMedia),
|
||||
'reuploadRequestedBy': serializer.toJson<List<int>?>(reuploadRequestedBy),
|
||||
|
|
@ -2323,7 +2343,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
Value<UploadState?> uploadState = const Value.absent(),
|
||||
Value<DownloadState?> downloadState = const Value.absent(),
|
||||
bool? requiresAuthentication,
|
||||
bool? reopenByContact,
|
||||
bool? stored,
|
||||
bool? isDraftMedia,
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -2342,7 +2361,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
downloadState.present ? downloadState.value : this.downloadState,
|
||||
requiresAuthentication:
|
||||
requiresAuthentication ?? this.requiresAuthentication,
|
||||
reopenByContact: reopenByContact ?? this.reopenByContact,
|
||||
stored: stored ?? this.stored,
|
||||
isDraftMedia: isDraftMedia ?? this.isDraftMedia,
|
||||
reuploadRequestedBy: reuploadRequestedBy.present
|
||||
|
|
@ -2375,9 +2393,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
requiresAuthentication: data.requiresAuthentication.present
|
||||
? data.requiresAuthentication.value
|
||||
: this.requiresAuthentication,
|
||||
reopenByContact: data.reopenByContact.present
|
||||
? data.reopenByContact.value
|
||||
: this.reopenByContact,
|
||||
stored: data.stored.present ? data.stored.value : this.stored,
|
||||
isDraftMedia: data.isDraftMedia.present
|
||||
? data.isDraftMedia.value
|
||||
|
|
@ -2414,7 +2429,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
..write('uploadState: $uploadState, ')
|
||||
..write('downloadState: $downloadState, ')
|
||||
..write('requiresAuthentication: $requiresAuthentication, ')
|
||||
..write('reopenByContact: $reopenByContact, ')
|
||||
..write('stored: $stored, ')
|
||||
..write('isDraftMedia: $isDraftMedia, ')
|
||||
..write('reuploadRequestedBy: $reuploadRequestedBy, ')
|
||||
|
|
@ -2436,7 +2450,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
uploadState,
|
||||
downloadState,
|
||||
requiresAuthentication,
|
||||
reopenByContact,
|
||||
stored,
|
||||
isDraftMedia,
|
||||
reuploadRequestedBy,
|
||||
|
|
@ -2456,7 +2469,6 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
other.uploadState == this.uploadState &&
|
||||
other.downloadState == this.downloadState &&
|
||||
other.requiresAuthentication == this.requiresAuthentication &&
|
||||
other.reopenByContact == this.reopenByContact &&
|
||||
other.stored == this.stored &&
|
||||
other.isDraftMedia == this.isDraftMedia &&
|
||||
other.reuploadRequestedBy == this.reuploadRequestedBy &&
|
||||
|
|
@ -2476,7 +2488,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
final Value<UploadState?> uploadState;
|
||||
final Value<DownloadState?> downloadState;
|
||||
final Value<bool> requiresAuthentication;
|
||||
final Value<bool> reopenByContact;
|
||||
final Value<bool> stored;
|
||||
final Value<bool> isDraftMedia;
|
||||
final Value<List<int>?> reuploadRequestedBy;
|
||||
|
|
@ -2494,7 +2505,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.uploadState = const Value.absent(),
|
||||
this.downloadState = const Value.absent(),
|
||||
this.requiresAuthentication = const Value.absent(),
|
||||
this.reopenByContact = const Value.absent(),
|
||||
this.stored = const Value.absent(),
|
||||
this.isDraftMedia = const Value.absent(),
|
||||
this.reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -2513,7 +2523,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.uploadState = const Value.absent(),
|
||||
this.downloadState = const Value.absent(),
|
||||
this.requiresAuthentication = const Value.absent(),
|
||||
this.reopenByContact = const Value.absent(),
|
||||
this.stored = const Value.absent(),
|
||||
this.isDraftMedia = const Value.absent(),
|
||||
this.reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -2533,7 +2542,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
Expression<String>? uploadState,
|
||||
Expression<String>? downloadState,
|
||||
Expression<bool>? requiresAuthentication,
|
||||
Expression<bool>? reopenByContact,
|
||||
Expression<bool>? stored,
|
||||
Expression<bool>? isDraftMedia,
|
||||
Expression<String>? reuploadRequestedBy,
|
||||
|
|
@ -2553,7 +2561,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
if (downloadState != null) 'download_state': downloadState,
|
||||
if (requiresAuthentication != null)
|
||||
'requires_authentication': requiresAuthentication,
|
||||
if (reopenByContact != null) 'reopen_by_contact': reopenByContact,
|
||||
if (stored != null) 'stored': stored,
|
||||
if (isDraftMedia != null) 'is_draft_media': isDraftMedia,
|
||||
if (reuploadRequestedBy != null)
|
||||
|
|
@ -2576,7 +2583,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
Value<UploadState?>? uploadState,
|
||||
Value<DownloadState?>? downloadState,
|
||||
Value<bool>? requiresAuthentication,
|
||||
Value<bool>? reopenByContact,
|
||||
Value<bool>? stored,
|
||||
Value<bool>? isDraftMedia,
|
||||
Value<List<int>?>? reuploadRequestedBy,
|
||||
|
|
@ -2595,7 +2601,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
downloadState: downloadState ?? this.downloadState,
|
||||
requiresAuthentication:
|
||||
requiresAuthentication ?? this.requiresAuthentication,
|
||||
reopenByContact: reopenByContact ?? this.reopenByContact,
|
||||
stored: stored ?? this.stored,
|
||||
isDraftMedia: isDraftMedia ?? this.isDraftMedia,
|
||||
reuploadRequestedBy: reuploadRequestedBy ?? this.reuploadRequestedBy,
|
||||
|
|
@ -2633,9 +2638,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
map['requires_authentication'] =
|
||||
Variable<bool>(requiresAuthentication.value);
|
||||
}
|
||||
if (reopenByContact.present) {
|
||||
map['reopen_by_contact'] = Variable<bool>(reopenByContact.value);
|
||||
}
|
||||
if (stored.present) {
|
||||
map['stored'] = Variable<bool>(stored.value);
|
||||
}
|
||||
|
|
@ -2683,7 +2685,6 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
..write('uploadState: $uploadState, ')
|
||||
..write('downloadState: $downloadState, ')
|
||||
..write('requiresAuthentication: $requiresAuthentication, ')
|
||||
..write('reopenByContact: $reopenByContact, ')
|
||||
..write('stored: $stored, ')
|
||||
..write('isDraftMedia: $isDraftMedia, ')
|
||||
..write('reuploadRequestedBy: $reuploadRequestedBy, ')
|
||||
|
|
@ -2759,6 +2760,16 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("media_stored" IN (0, 1))'),
|
||||
defaultValue: const Constant(false));
|
||||
static const VerificationMeta _mediaReopenedMeta =
|
||||
const VerificationMeta('mediaReopened');
|
||||
@override
|
||||
late final GeneratedColumn<bool> mediaReopened = GeneratedColumn<bool>(
|
||||
'media_reopened', aliasedName, false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("media_reopened" IN (0, 1))'),
|
||||
defaultValue: const Constant(false));
|
||||
static const VerificationMeta _downloadTokenMeta =
|
||||
const VerificationMeta('downloadToken');
|
||||
@override
|
||||
|
|
@ -2828,6 +2839,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
content,
|
||||
mediaId,
|
||||
mediaStored,
|
||||
mediaReopened,
|
||||
downloadToken,
|
||||
quotesMessageId,
|
||||
isDeletedFromSender,
|
||||
|
|
@ -2878,6 +2890,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
mediaStored.isAcceptableOrUnknown(
|
||||
data['media_stored']!, _mediaStoredMeta));
|
||||
}
|
||||
if (data.containsKey('media_reopened')) {
|
||||
context.handle(
|
||||
_mediaReopenedMeta,
|
||||
mediaReopened.isAcceptableOrUnknown(
|
||||
data['media_reopened']!, _mediaReopenedMeta));
|
||||
}
|
||||
if (data.containsKey('download_token')) {
|
||||
context.handle(
|
||||
_downloadTokenMeta,
|
||||
|
|
@ -2951,6 +2969,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
.read(DriftSqlType.string, data['${effectivePrefix}media_id']),
|
||||
mediaStored: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!,
|
||||
mediaReopened: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}media_reopened'])!,
|
||||
downloadToken: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.blob, data['${effectivePrefix}download_token']),
|
||||
quotesMessageId: attachedDatabase.typeMapping.read(
|
||||
|
|
@ -2989,6 +3009,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
final String? content;
|
||||
final String? mediaId;
|
||||
final bool mediaStored;
|
||||
final bool mediaReopened;
|
||||
final Uint8List? downloadToken;
|
||||
final String? quotesMessageId;
|
||||
final bool isDeletedFromSender;
|
||||
|
|
@ -3006,6 +3027,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
this.content,
|
||||
this.mediaId,
|
||||
required this.mediaStored,
|
||||
required this.mediaReopened,
|
||||
this.downloadToken,
|
||||
this.quotesMessageId,
|
||||
required this.isDeletedFromSender,
|
||||
|
|
@ -3033,6 +3055,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
map['media_id'] = Variable<String>(mediaId);
|
||||
}
|
||||
map['media_stored'] = Variable<bool>(mediaStored);
|
||||
map['media_reopened'] = Variable<bool>(mediaReopened);
|
||||
if (!nullToAbsent || downloadToken != null) {
|
||||
map['download_token'] = Variable<Uint8List>(downloadToken);
|
||||
}
|
||||
|
|
@ -3074,6 +3097,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
? const Value.absent()
|
||||
: Value(mediaId),
|
||||
mediaStored: Value(mediaStored),
|
||||
mediaReopened: Value(mediaReopened),
|
||||
downloadToken: downloadToken == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(downloadToken),
|
||||
|
|
@ -3112,6 +3136,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
content: serializer.fromJson<String?>(json['content']),
|
||||
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
||||
mediaStored: serializer.fromJson<bool>(json['mediaStored']),
|
||||
mediaReopened: serializer.fromJson<bool>(json['mediaReopened']),
|
||||
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
||||
quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']),
|
||||
isDeletedFromSender:
|
||||
|
|
@ -3136,6 +3161,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
'content': serializer.toJson<String?>(content),
|
||||
'mediaId': serializer.toJson<String?>(mediaId),
|
||||
'mediaStored': serializer.toJson<bool>(mediaStored),
|
||||
'mediaReopened': serializer.toJson<bool>(mediaReopened),
|
||||
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
||||
'quotesMessageId': serializer.toJson<String?>(quotesMessageId),
|
||||
'isDeletedFromSender': serializer.toJson<bool>(isDeletedFromSender),
|
||||
|
|
@ -3156,6 +3182,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
bool? mediaStored,
|
||||
bool? mediaReopened,
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<String?> quotesMessageId = const Value.absent(),
|
||||
bool? isDeletedFromSender,
|
||||
|
|
@ -3173,6 +3200,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
content: content.present ? content.value : this.content,
|
||||
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
||||
mediaStored: mediaStored ?? this.mediaStored,
|
||||
mediaReopened: mediaReopened ?? this.mediaReopened,
|
||||
downloadToken:
|
||||
downloadToken.present ? downloadToken.value : this.downloadToken,
|
||||
quotesMessageId: quotesMessageId.present
|
||||
|
|
@ -3196,6 +3224,9 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
||||
mediaStored:
|
||||
data.mediaStored.present ? data.mediaStored.value : this.mediaStored,
|
||||
mediaReopened: data.mediaReopened.present
|
||||
? data.mediaReopened.value
|
||||
: this.mediaReopened,
|
||||
downloadToken: data.downloadToken.present
|
||||
? data.downloadToken.value
|
||||
: this.downloadToken,
|
||||
|
|
@ -3227,6 +3258,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
..write('content: $content, ')
|
||||
..write('mediaId: $mediaId, ')
|
||||
..write('mediaStored: $mediaStored, ')
|
||||
..write('mediaReopened: $mediaReopened, ')
|
||||
..write('downloadToken: $downloadToken, ')
|
||||
..write('quotesMessageId: $quotesMessageId, ')
|
||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||
|
|
@ -3249,6 +3281,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
content,
|
||||
mediaId,
|
||||
mediaStored,
|
||||
mediaReopened,
|
||||
$driftBlobEquality.hash(downloadToken),
|
||||
quotesMessageId,
|
||||
isDeletedFromSender,
|
||||
|
|
@ -3269,6 +3302,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
other.content == this.content &&
|
||||
other.mediaId == this.mediaId &&
|
||||
other.mediaStored == this.mediaStored &&
|
||||
other.mediaReopened == this.mediaReopened &&
|
||||
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
||||
other.quotesMessageId == this.quotesMessageId &&
|
||||
other.isDeletedFromSender == this.isDeletedFromSender &&
|
||||
|
|
@ -3288,6 +3322,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
final Value<String?> content;
|
||||
final Value<String?> mediaId;
|
||||
final Value<bool> mediaStored;
|
||||
final Value<bool> mediaReopened;
|
||||
final Value<Uint8List?> downloadToken;
|
||||
final Value<String?> quotesMessageId;
|
||||
final Value<bool> isDeletedFromSender;
|
||||
|
|
@ -3306,6 +3341,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
this.content = const Value.absent(),
|
||||
this.mediaId = const Value.absent(),
|
||||
this.mediaStored = const Value.absent(),
|
||||
this.mediaReopened = const Value.absent(),
|
||||
this.downloadToken = const Value.absent(),
|
||||
this.quotesMessageId = const Value.absent(),
|
||||
this.isDeletedFromSender = const Value.absent(),
|
||||
|
|
@ -3325,6 +3361,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
this.content = const Value.absent(),
|
||||
this.mediaId = const Value.absent(),
|
||||
this.mediaStored = const Value.absent(),
|
||||
this.mediaReopened = const Value.absent(),
|
||||
this.downloadToken = const Value.absent(),
|
||||
this.quotesMessageId = const Value.absent(),
|
||||
this.isDeletedFromSender = const Value.absent(),
|
||||
|
|
@ -3346,6 +3383,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
Expression<String>? content,
|
||||
Expression<String>? mediaId,
|
||||
Expression<bool>? mediaStored,
|
||||
Expression<bool>? mediaReopened,
|
||||
Expression<Uint8List>? downloadToken,
|
||||
Expression<String>? quotesMessageId,
|
||||
Expression<bool>? isDeletedFromSender,
|
||||
|
|
@ -3365,6 +3403,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
if (content != null) 'content': content,
|
||||
if (mediaId != null) 'media_id': mediaId,
|
||||
if (mediaStored != null) 'media_stored': mediaStored,
|
||||
if (mediaReopened != null) 'media_reopened': mediaReopened,
|
||||
if (downloadToken != null) 'download_token': downloadToken,
|
||||
if (quotesMessageId != null) 'quotes_message_id': quotesMessageId,
|
||||
if (isDeletedFromSender != null)
|
||||
|
|
@ -3387,6 +3426,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
Value<String?>? content,
|
||||
Value<String?>? mediaId,
|
||||
Value<bool>? mediaStored,
|
||||
Value<bool>? mediaReopened,
|
||||
Value<Uint8List?>? downloadToken,
|
||||
Value<String?>? quotesMessageId,
|
||||
Value<bool>? isDeletedFromSender,
|
||||
|
|
@ -3405,6 +3445,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
content: content ?? this.content,
|
||||
mediaId: mediaId ?? this.mediaId,
|
||||
mediaStored: mediaStored ?? this.mediaStored,
|
||||
mediaReopened: mediaReopened ?? this.mediaReopened,
|
||||
downloadToken: downloadToken ?? this.downloadToken,
|
||||
quotesMessageId: quotesMessageId ?? this.quotesMessageId,
|
||||
isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender,
|
||||
|
|
@ -3443,6 +3484,9 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
if (mediaStored.present) {
|
||||
map['media_stored'] = Variable<bool>(mediaStored.value);
|
||||
}
|
||||
if (mediaReopened.present) {
|
||||
map['media_reopened'] = Variable<bool>(mediaReopened.value);
|
||||
}
|
||||
if (downloadToken.present) {
|
||||
map['download_token'] = Variable<Uint8List>(downloadToken.value);
|
||||
}
|
||||
|
|
@ -3486,6 +3530,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
..write('content: $content, ')
|
||||
..write('mediaId: $mediaId, ')
|
||||
..write('mediaStored: $mediaStored, ')
|
||||
..write('mediaReopened: $mediaReopened, ')
|
||||
..write('downloadToken: $downloadToken, ')
|
||||
..write('quotesMessageId: $quotesMessageId, ')
|
||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||
|
|
@ -8495,6 +8540,7 @@ typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
|||
Value<Uint8List?> stateEncryptionKey,
|
||||
Value<Uint8List?> myGroupPrivateKey,
|
||||
required String groupName,
|
||||
Value<String?> draftMessage,
|
||||
Value<int> totalMediaCounter,
|
||||
Value<bool> alsoBestFriend,
|
||||
Value<int> deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -8522,6 +8568,7 @@ typedef $$GroupsTableUpdateCompanionBuilder = GroupsCompanion Function({
|
|||
Value<Uint8List?> stateEncryptionKey,
|
||||
Value<Uint8List?> myGroupPrivateKey,
|
||||
Value<String> groupName,
|
||||
Value<String?> draftMessage,
|
||||
Value<int> totalMediaCounter,
|
||||
Value<bool> alsoBestFriend,
|
||||
Value<int> deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -8637,6 +8684,9 @@ class $$GroupsTableFilterComposer extends Composer<_$TwonlyDB, $GroupsTable> {
|
|||
ColumnFilters<String> get groupName => $composableBuilder(
|
||||
column: $table.groupName, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get draftMessage => $composableBuilder(
|
||||
column: $table.draftMessage, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<int> get totalMediaCounter => $composableBuilder(
|
||||
column: $table.totalMediaCounter,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
|
@ -8796,6 +8846,10 @@ class $$GroupsTableOrderingComposer extends Composer<_$TwonlyDB, $GroupsTable> {
|
|||
ColumnOrderings<String> get groupName => $composableBuilder(
|
||||
column: $table.groupName, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get draftMessage => $composableBuilder(
|
||||
column: $table.draftMessage,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<int> get totalMediaCounter => $composableBuilder(
|
||||
column: $table.totalMediaCounter,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
|
@ -8890,6 +8944,9 @@ class $$GroupsTableAnnotationComposer
|
|||
GeneratedColumn<String> get groupName =>
|
||||
$composableBuilder(column: $table.groupName, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get draftMessage => $composableBuilder(
|
||||
column: $table.draftMessage, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<int> get totalMediaCounter => $composableBuilder(
|
||||
column: $table.totalMediaCounter, builder: (column) => column);
|
||||
|
||||
|
|
@ -9028,6 +9085,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
|||
Value<Uint8List?> stateEncryptionKey = const Value.absent(),
|
||||
Value<Uint8List?> myGroupPrivateKey = const Value.absent(),
|
||||
Value<String> groupName = const Value.absent(),
|
||||
Value<String?> draftMessage = const Value.absent(),
|
||||
Value<int> totalMediaCounter = const Value.absent(),
|
||||
Value<bool> alsoBestFriend = const Value.absent(),
|
||||
Value<int> deleteMessagesAfterMilliseconds = const Value.absent(),
|
||||
|
|
@ -9055,6 +9113,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
|||
stateEncryptionKey: stateEncryptionKey,
|
||||
myGroupPrivateKey: myGroupPrivateKey,
|
||||
groupName: groupName,
|
||||
draftMessage: draftMessage,
|
||||
totalMediaCounter: totalMediaCounter,
|
||||
alsoBestFriend: alsoBestFriend,
|
||||
deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -9082,6 +9141,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
|||
Value<Uint8List?> stateEncryptionKey = const Value.absent(),
|
||||
Value<Uint8List?> myGroupPrivateKey = const Value.absent(),
|
||||
required String groupName,
|
||||
Value<String?> draftMessage = const Value.absent(),
|
||||
Value<int> totalMediaCounter = const Value.absent(),
|
||||
Value<bool> alsoBestFriend = const Value.absent(),
|
||||
Value<int> deleteMessagesAfterMilliseconds = const Value.absent(),
|
||||
|
|
@ -9109,6 +9169,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
|||
stateEncryptionKey: stateEncryptionKey,
|
||||
myGroupPrivateKey: myGroupPrivateKey,
|
||||
groupName: groupName,
|
||||
draftMessage: draftMessage,
|
||||
totalMediaCounter: totalMediaCounter,
|
||||
alsoBestFriend: alsoBestFriend,
|
||||
deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds,
|
||||
|
|
@ -9203,7 +9264,6 @@ typedef $$MediaFilesTableCreateCompanionBuilder = MediaFilesCompanion Function({
|
|||
Value<UploadState?> uploadState,
|
||||
Value<DownloadState?> downloadState,
|
||||
Value<bool> requiresAuthentication,
|
||||
Value<bool> reopenByContact,
|
||||
Value<bool> stored,
|
||||
Value<bool> isDraftMedia,
|
||||
Value<List<int>?> reuploadRequestedBy,
|
||||
|
|
@ -9222,7 +9282,6 @@ typedef $$MediaFilesTableUpdateCompanionBuilder = MediaFilesCompanion Function({
|
|||
Value<UploadState?> uploadState,
|
||||
Value<DownloadState?> downloadState,
|
||||
Value<bool> requiresAuthentication,
|
||||
Value<bool> reopenByContact,
|
||||
Value<bool> stored,
|
||||
Value<bool> isDraftMedia,
|
||||
Value<List<int>?> reuploadRequestedBy,
|
||||
|
|
@ -9287,10 +9346,6 @@ class $$MediaFilesTableFilterComposer
|
|||
column: $table.requiresAuthentication,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get reopenByContact => $composableBuilder(
|
||||
column: $table.reopenByContact,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get stored => $composableBuilder(
|
||||
column: $table.stored, builder: (column) => ColumnFilters(column));
|
||||
|
||||
|
|
@ -9373,10 +9428,6 @@ class $$MediaFilesTableOrderingComposer
|
|||
column: $table.requiresAuthentication,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get reopenByContact => $composableBuilder(
|
||||
column: $table.reopenByContact,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get stored => $composableBuilder(
|
||||
column: $table.stored, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
|
|
@ -9441,9 +9492,6 @@ class $$MediaFilesTableAnnotationComposer
|
|||
GeneratedColumn<bool> get requiresAuthentication => $composableBuilder(
|
||||
column: $table.requiresAuthentication, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get reopenByContact => $composableBuilder(
|
||||
column: $table.reopenByContact, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get stored =>
|
||||
$composableBuilder(column: $table.stored, builder: (column) => column);
|
||||
|
||||
|
|
@ -9525,7 +9573,6 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
Value<UploadState?> uploadState = const Value.absent(),
|
||||
Value<DownloadState?> downloadState = const Value.absent(),
|
||||
Value<bool> requiresAuthentication = const Value.absent(),
|
||||
Value<bool> reopenByContact = const Value.absent(),
|
||||
Value<bool> stored = const Value.absent(),
|
||||
Value<bool> isDraftMedia = const Value.absent(),
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -9544,7 +9591,6 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
uploadState: uploadState,
|
||||
downloadState: downloadState,
|
||||
requiresAuthentication: requiresAuthentication,
|
||||
reopenByContact: reopenByContact,
|
||||
stored: stored,
|
||||
isDraftMedia: isDraftMedia,
|
||||
reuploadRequestedBy: reuploadRequestedBy,
|
||||
|
|
@ -9563,7 +9609,6 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
Value<UploadState?> uploadState = const Value.absent(),
|
||||
Value<DownloadState?> downloadState = const Value.absent(),
|
||||
Value<bool> requiresAuthentication = const Value.absent(),
|
||||
Value<bool> reopenByContact = const Value.absent(),
|
||||
Value<bool> stored = const Value.absent(),
|
||||
Value<bool> isDraftMedia = const Value.absent(),
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -9582,7 +9627,6 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
uploadState: uploadState,
|
||||
downloadState: downloadState,
|
||||
requiresAuthentication: requiresAuthentication,
|
||||
reopenByContact: reopenByContact,
|
||||
stored: stored,
|
||||
isDraftMedia: isDraftMedia,
|
||||
reuploadRequestedBy: reuploadRequestedBy,
|
||||
|
|
@ -9648,6 +9692,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
|
|||
Value<String?> content,
|
||||
Value<String?> mediaId,
|
||||
Value<bool> mediaStored,
|
||||
Value<bool> mediaReopened,
|
||||
Value<Uint8List?> downloadToken,
|
||||
Value<String?> quotesMessageId,
|
||||
Value<bool> isDeletedFromSender,
|
||||
|
|
@ -9667,6 +9712,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
|
|||
Value<String?> content,
|
||||
Value<String?> mediaId,
|
||||
Value<bool> mediaStored,
|
||||
Value<bool> mediaReopened,
|
||||
Value<Uint8List?> downloadToken,
|
||||
Value<String?> quotesMessageId,
|
||||
Value<bool> isDeletedFromSender,
|
||||
|
|
@ -9817,6 +9863,9 @@ class $$MessagesTableFilterComposer
|
|||
ColumnFilters<bool> get mediaStored => $composableBuilder(
|
||||
column: $table.mediaStored, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get mediaReopened => $composableBuilder(
|
||||
column: $table.mediaReopened, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken, builder: (column) => ColumnFilters(column));
|
||||
|
||||
|
|
@ -10012,6 +10061,10 @@ class $$MessagesTableOrderingComposer
|
|||
ColumnOrderings<bool> get mediaStored => $composableBuilder(
|
||||
column: $table.mediaStored, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get mediaReopened => $composableBuilder(
|
||||
column: $table.mediaReopened,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
|
@ -10124,6 +10177,9 @@ class $$MessagesTableAnnotationComposer
|
|||
GeneratedColumn<bool> get mediaStored => $composableBuilder(
|
||||
column: $table.mediaStored, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get mediaReopened => $composableBuilder(
|
||||
column: $table.mediaReopened, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken, builder: (column) => column);
|
||||
|
||||
|
|
@ -10333,6 +10389,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<bool> mediaStored = const Value.absent(),
|
||||
Value<bool> mediaReopened = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<String?> quotesMessageId = const Value.absent(),
|
||||
Value<bool> isDeletedFromSender = const Value.absent(),
|
||||
|
|
@ -10352,6 +10409,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
content: content,
|
||||
mediaId: mediaId,
|
||||
mediaStored: mediaStored,
|
||||
mediaReopened: mediaReopened,
|
||||
downloadToken: downloadToken,
|
||||
quotesMessageId: quotesMessageId,
|
||||
isDeletedFromSender: isDeletedFromSender,
|
||||
|
|
@ -10371,6 +10429,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<bool> mediaStored = const Value.absent(),
|
||||
Value<bool> mediaReopened = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<String?> quotesMessageId = const Value.absent(),
|
||||
Value<bool> isDeletedFromSender = const Value.absent(),
|
||||
|
|
@ -10390,6 +10449,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
content: content,
|
||||
mediaId: mediaId,
|
||||
mediaStored: mediaStored,
|
||||
mediaReopened: mediaReopened,
|
||||
downloadToken: downloadToken,
|
||||
quotesMessageId: quotesMessageId,
|
||||
isDeletedFromSender: isDeletedFromSender,
|
||||
|
|
|
|||
1599
lib/src/database/twonly.db.steps.dart
Normal file
1599
lib/src/database/twonly.db.steps.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -822,5 +822,12 @@
|
|||
"dialogAskDeleteMediaFilePopTitle": "Bist du sicher, dass du dein Meisterwerk löschen möchtest?",
|
||||
"dialogAskDeleteMediaFilePopDelete": "Löschen",
|
||||
"allowErrorTracking": "Fehler und Crashes mit uns teilen",
|
||||
"allowErrorTrackingSubtitle": "Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen."
|
||||
"allowErrorTrackingSubtitle": "Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen.",
|
||||
"avatarSaveChanges": "Möchtest du die Änderungen speichern?",
|
||||
"avatarSaveChangesStore": "Speichern",
|
||||
"avatarSaveChangesDiscard": "Verwerfen",
|
||||
"inProcess": "Wird verarbeitet",
|
||||
"draftMessage": "Entwurf",
|
||||
"exportMemories": "Memories exportieren (Beta)",
|
||||
"importMemories": "Memories importieren (Beta)"
|
||||
}
|
||||
|
|
@ -600,5 +600,12 @@
|
|||
"dialogAskDeleteMediaFilePopTitle": "Are you sure you want to delete your masterpiece?",
|
||||
"dialogAskDeleteMediaFilePopDelete": "Delete",
|
||||
"allowErrorTracking": "Share errors and crashes with us",
|
||||
"allowErrorTrackingSubtitle": "If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded."
|
||||
"allowErrorTrackingSubtitle": "If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.",
|
||||
"avatarSaveChanges": "Would you like to save the changes?",
|
||||
"avatarSaveChangesStore": "Save",
|
||||
"avatarSaveChangesDiscard": "Discard",
|
||||
"inProcess": "In process",
|
||||
"draftMessage": "Draft",
|
||||
"exportMemories": "Export memories (Beta)",
|
||||
"importMemories": "Import memories (Beta)"
|
||||
}
|
||||
|
|
@ -2707,6 +2707,48 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.'**
|
||||
String get allowErrorTrackingSubtitle;
|
||||
|
||||
/// No description provided for @avatarSaveChanges.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Would you like to save the changes?'**
|
||||
String get avatarSaveChanges;
|
||||
|
||||
/// No description provided for @avatarSaveChangesStore.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save'**
|
||||
String get avatarSaveChangesStore;
|
||||
|
||||
/// No description provided for @avatarSaveChangesDiscard.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Discard'**
|
||||
String get avatarSaveChangesDiscard;
|
||||
|
||||
/// No description provided for @inProcess.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'In process'**
|
||||
String get inProcess;
|
||||
|
||||
/// No description provided for @draftMessage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Draft'**
|
||||
String get draftMessage;
|
||||
|
||||
/// No description provided for @exportMemories.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Export memories (Beta)'**
|
||||
String get exportMemories;
|
||||
|
||||
/// No description provided for @importMemories.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Import memories (Beta)'**
|
||||
String get importMemories;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1495,4 +1495,25 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get allowErrorTrackingSubtitle =>
|
||||
'Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen.';
|
||||
|
||||
@override
|
||||
String get avatarSaveChanges => 'Möchtest du die Änderungen speichern?';
|
||||
|
||||
@override
|
||||
String get avatarSaveChangesStore => 'Speichern';
|
||||
|
||||
@override
|
||||
String get avatarSaveChangesDiscard => 'Verwerfen';
|
||||
|
||||
@override
|
||||
String get inProcess => 'Wird verarbeitet';
|
||||
|
||||
@override
|
||||
String get draftMessage => 'Entwurf';
|
||||
|
||||
@override
|
||||
String get exportMemories => 'Memories exportieren (Beta)';
|
||||
|
||||
@override
|
||||
String get importMemories => 'Memories importieren (Beta)';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1485,4 +1485,25 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get allowErrorTrackingSubtitle =>
|
||||
'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.';
|
||||
|
||||
@override
|
||||
String get avatarSaveChanges => 'Would you like to save the changes?';
|
||||
|
||||
@override
|
||||
String get avatarSaveChangesStore => 'Save';
|
||||
|
||||
@override
|
||||
String get avatarSaveChangesDiscard => 'Discard';
|
||||
|
||||
@override
|
||||
String get inProcess => 'In process';
|
||||
|
||||
@override
|
||||
String get draftMessage => 'Draft';
|
||||
|
||||
@override
|
||||
String get exportMemories => 'Export memories (Beta)';
|
||||
|
||||
@override
|
||||
String get importMemories => 'Import memories (Beta)';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ import 'package:twonly/src/utils/log.dart';
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
final lockConnecting = Mutex();
|
||||
final lockRetransStore = Mutex();
|
||||
|
|
@ -78,12 +77,7 @@ class ApiService {
|
|||
await _channel!.ready;
|
||||
Log.info('websocket connected to $apiUrl');
|
||||
return true;
|
||||
} on WebSocketChannelException catch (e) {
|
||||
if (!e.message
|
||||
.toString()
|
||||
.contains('No address associated with hostname')) {
|
||||
Log.error('could not connect to api got: $e');
|
||||
}
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -311,17 +305,17 @@ class ApiService {
|
|||
|
||||
final res = asResult(await _waitForResponse(seq));
|
||||
if (res.isError) {
|
||||
Log.error('got error from server: ${res.error}');
|
||||
Log.warn('Got error from server: ${res.error}');
|
||||
if (res.error == ErrorCode.AppVersionOutdated) {
|
||||
globalCallbackAppIsOutdated();
|
||||
Log.error('App Version is OUTDATED.');
|
||||
Log.warn('App Version is OUTDATED.');
|
||||
appIsOutdated = true;
|
||||
await close(() {});
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
if (res.error == ErrorCode.NewDeviceRegistered) {
|
||||
globalCallbackNewDeviceRegistered();
|
||||
Log.error(
|
||||
Log.warn(
|
||||
'Device is disabled, as a newer device restore twonly Backup.',
|
||||
);
|
||||
appIsOutdated = true;
|
||||
|
|
@ -336,13 +330,13 @@ class ApiService {
|
|||
// this will send the request one more time.
|
||||
return sendRequestSync(request, authenticated: false);
|
||||
} else {
|
||||
Log.error('session is not authenticated');
|
||||
Log.warn('Session is not authenticated');
|
||||
return Result.error(ErrorCode.InternalError);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
||||
Log.error('Contact deleted their account $contactId.');
|
||||
Log.warn('Contact deleted their account $contactId.');
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(contactId)
|
||||
.getSingleOrNull();
|
||||
|
|
|
|||
|
|
@ -148,16 +148,22 @@ Future<void> handleMediaUpdate(
|
|||
switch (mediaUpdate.type) {
|
||||
case EncryptedContent_MediaUpdate_Type.REOPENED:
|
||||
Log.info('Got media file reopened ${mediaFile.mediaId}');
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
const MediaFilesCompanion(
|
||||
reopenByContact: Value(true),
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
message.messageId,
|
||||
const MessagesCompanion(
|
||||
mediaReopened: Value(true),
|
||||
),
|
||||
);
|
||||
case EncryptedContent_MediaUpdate_Type.STORED:
|
||||
Log.info('Got media file stored ${mediaFile.mediaId}');
|
||||
final mediaService = await MediaFileService.fromMedia(mediaFile);
|
||||
await mediaService.storeMediaFile();
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
message.messageId,
|
||||
const MessagesCompanion(
|
||||
mediaStored: Value(true),
|
||||
),
|
||||
);
|
||||
|
||||
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
|
||||
Log.info('Got media file decryption error ${mediaFile.mediaId}');
|
||||
|
|
|
|||
|
|
@ -13,33 +13,45 @@ Future<void> handleMessageUpdate(
|
|||
Log.info(
|
||||
'Opened message $targetMessageId',
|
||||
);
|
||||
await twonlyDB.messagesDao.handleMessageOpened(
|
||||
contactId,
|
||||
targetMessageId,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
try {
|
||||
await twonlyDB.messagesDao.handleMessageOpened(
|
||||
contactId,
|
||||
targetMessageId,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.warn(e);
|
||||
}
|
||||
}
|
||||
case EncryptedContent_MessageUpdate_Type.DELETE:
|
||||
if (!await isSender(contactId, messageUpdate.senderMessageId)) {
|
||||
return;
|
||||
}
|
||||
Log.info('Delete message ${messageUpdate.senderMessageId}');
|
||||
await twonlyDB.messagesDao.handleMessageDeletion(
|
||||
contactId,
|
||||
messageUpdate.senderMessageId,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
try {
|
||||
await twonlyDB.messagesDao.handleMessageDeletion(
|
||||
contactId,
|
||||
messageUpdate.senderMessageId,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.warn(e);
|
||||
}
|
||||
case EncryptedContent_MessageUpdate_Type.EDIT_TEXT:
|
||||
if (!await isSender(contactId, messageUpdate.senderMessageId)) {
|
||||
return;
|
||||
}
|
||||
Log.info('Edit message ${messageUpdate.senderMessageId}');
|
||||
await twonlyDB.messagesDao.handleTextEdit(
|
||||
contactId,
|
||||
messageUpdate.senderMessageId,
|
||||
messageUpdate.text,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
try {
|
||||
await twonlyDB.messagesDao.handleTextEdit(
|
||||
contactId,
|
||||
messageUpdate.senderMessageId,
|
||||
messageUpdate.text,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Future<void> finishStartedPreprocessing() async {
|
|||
}
|
||||
await startBackgroundMediaUpload(service);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
Log.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
|||
);
|
||||
|
||||
if (resp.isError) {
|
||||
Log.error('Could not transmit message $receiptId got ${resp.error}.');
|
||||
Log.warn('Could not transmit message got ${resp.error}.');
|
||||
if (resp.error == ErrorCode.UserIdNotFound) {
|
||||
await twonlyDB.receiptsDao.deleteReceipt(receiptId);
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
|
|
@ -162,6 +162,12 @@ Future<void> insertAndSendTextMessage(
|
|||
String textMessage,
|
||||
String? quotesMessageId,
|
||||
) async {
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
groupId,
|
||||
const GroupsCompanion(
|
||||
draftMessage: Value(null),
|
||||
),
|
||||
);
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
groupId: Value(groupId),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
|
@ -63,7 +62,7 @@ Future<void> handleClient2ClientMessage(int fromUserId, Uint8List body) async {
|
|||
|
||||
await protectReceiptCheck.protect(() async {
|
||||
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
|
||||
Log.error('Got duplicated message from the server. Ignoring it.');
|
||||
Log.warn('Got duplicated message from the server.');
|
||||
return;
|
||||
}
|
||||
await twonlyDB.receiptsDao.gotReceipt(receiptId);
|
||||
|
|
@ -135,19 +134,23 @@ Future<void> handleClient2ClientMessage(int fromUserId, Uint8List body) async {
|
|||
..receiptId = receiptId
|
||||
..type = Message_Type.PLAINTEXT_CONTENT
|
||||
..plaintextContent = responsePlaintextContent;
|
||||
Log.error('Sending decryption error ($receiptId)');
|
||||
Log.error('Sending decryption error');
|
||||
} else {
|
||||
response = Message()..type = Message_Type.SENDER_DELIVERY_RECEIPT;
|
||||
}
|
||||
|
||||
await twonlyDB.receiptsDao.insertReceipt(
|
||||
ReceiptsCompanion(
|
||||
receiptId: Value(receiptId),
|
||||
contactId: Value(fromUserId),
|
||||
message: Value(response.writeToBuffer()),
|
||||
contactWillSendsReceipt: const Value(false),
|
||||
),
|
||||
);
|
||||
try {
|
||||
await twonlyDB.receiptsDao.insertReceipt(
|
||||
ReceiptsCompanion(
|
||||
receiptId: Value(receiptId),
|
||||
contactId: Value(fromUserId),
|
||||
message: Value(response.writeToBuffer()),
|
||||
contactWillSendsReceipt: const Value(false),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.warn(e);
|
||||
}
|
||||
await tryToSendCompleteMessage(receiptId: receiptId);
|
||||
}
|
||||
case Message_Type.TEST_NOTIFICATION:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Future<void> compressImage(
|
|||
|
||||
if (compressedBytes == null) {
|
||||
throw Exception(
|
||||
'Could not compress media file: $sourceFile. Sending original file.',
|
||||
'Could not compress media file: Sending original file.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ Future<void> compressImage(
|
|||
|
||||
await destinationFile.writeAsBytes(compressedBytes);
|
||||
} catch (e) {
|
||||
Log.error('$e');
|
||||
Log.warn('$e');
|
||||
sourceFile.copySync(destinationFile.path);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class MediaFileService {
|
|||
}
|
||||
|
||||
static Future<void> purgeTempFolder() async {
|
||||
final tempDirectory = MediaFileService._buildDirectoryPath(
|
||||
final tempDirectory = MediaFileService.buildDirectoryPath(
|
||||
'tmp',
|
||||
await getApplicationSupportDirectory(),
|
||||
);
|
||||
|
|
@ -224,12 +224,6 @@ class MediaFileService {
|
|||
stored: Value(true),
|
||||
),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessagesByMediaId(
|
||||
mediaFile.mediaId,
|
||||
const MessagesCompanion(
|
||||
mediaStored: Value(true),
|
||||
),
|
||||
);
|
||||
|
||||
if (originalPath.existsSync() && !tempPath.existsSync()) {
|
||||
await compressMedia();
|
||||
|
|
@ -245,7 +239,7 @@ class MediaFileService {
|
|||
await updateFromDB();
|
||||
}
|
||||
|
||||
static Directory _buildDirectoryPath(
|
||||
static Directory buildDirectoryPath(
|
||||
String directory,
|
||||
Directory applicationSupportDirectory,
|
||||
) {
|
||||
|
|
@ -281,7 +275,7 @@ class MediaFileService {
|
|||
}
|
||||
}
|
||||
final mediaBaseDir =
|
||||
_buildDirectoryPath(directory, applicationSupportDirectory);
|
||||
buildDirectoryPath(directory, applicationSupportDirectory);
|
||||
return File(
|
||||
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ Future<Uint8List?> encryptPushNotification(
|
|||
content.kind != PushKind.testNotification) {
|
||||
// this will be enforced after every app uses this system... :/
|
||||
// return null;
|
||||
Log.error('Using insecure key as the receiver does not send a push key!');
|
||||
Log.warn('Using insecure key as the receiver does not send a push key!');
|
||||
|
||||
await sendCipherText(
|
||||
toUserId,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ Future<void> removeTwonlySafeFromServer() async {
|
|||
);
|
||||
Log.info('Download deleted with: ${response.statusCode}');
|
||||
} catch (e) {
|
||||
Log.error('Could not connect to the server.');
|
||||
Log.error('Could not connect upload the backup.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
|
|||
return user;
|
||||
});
|
||||
} else if (update.status == TaskStatus.complete) {
|
||||
Log.error(
|
||||
Log.info(
|
||||
'twonly Backup uploaded with status code ${update.responseStatusCode}',
|
||||
);
|
||||
await updateUserdata((user) {
|
||||
|
|
|
|||
|
|
@ -264,7 +264,6 @@ bool isUUIDNewer(String uuid1, String uuid2) {
|
|||
final timestamp2 = int.parse(uuid2.substring(0, 8), radix: 16);
|
||||
return timestamp1 > timestamp2;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,10 +46,7 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
|||
_imageSaving = true;
|
||||
});
|
||||
|
||||
if (widget.mediaService.mediaFile.type == MediaType.image ||
|
||||
widget.mediaService.mediaFile.type == MediaType.gif) {
|
||||
await widget.storeImageAsOriginal();
|
||||
}
|
||||
await widget.storeImageAsOriginal();
|
||||
|
||||
String? res;
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
bool showWideAngleZoom = false;
|
||||
bool showWideAngleZoomIOS = false;
|
||||
bool _isDisposed = false;
|
||||
int? _wideCameraIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -51,7 +52,19 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
Future<void> initAsync() async {
|
||||
showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1;
|
||||
Log.info('Found ${gCameras.length} cameras for zoom.');
|
||||
if (!showWideAngleZoom && Platform.isIOS && gCameras.length == 3) {
|
||||
|
||||
var index =
|
||||
gCameras.indexWhere((t) => t.lensType == CameraLensType.ultraWide);
|
||||
if (index == -1) {
|
||||
index = gCameras.indexWhere(
|
||||
(t) => t.lensType == CameraLensType.wide,
|
||||
);
|
||||
}
|
||||
if (index != -1) {
|
||||
_wideCameraIndex = index;
|
||||
}
|
||||
|
||||
if (!showWideAngleZoom && Platform.isIOS && _wideCameraIndex != null) {
|
||||
showWideAngleZoomIOS = true;
|
||||
}
|
||||
if (_isDisposed) return;
|
||||
|
|
@ -76,10 +89,12 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
|
||||
const zoomTextStyle = TextStyle(fontSize: 13);
|
||||
final isSmallerFocused = widget.scaleFactor < 1 ||
|
||||
(showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == 2);
|
||||
(showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
||||
final isMiddleFocused = widget.scaleFactor >= 1 &&
|
||||
widget.scaleFactor < 2 &&
|
||||
!(showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == 2);
|
||||
!(showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
||||
|
||||
final maxLevel = max(
|
||||
min(widget.selectedCameraDetails.maxAvailableZoom, 2),
|
||||
|
|
@ -106,7 +121,9 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
),
|
||||
onPressed: () async {
|
||||
if (showWideAngleZoomIOS) {
|
||||
await widget.selectCamera(2, true);
|
||||
if (_wideCameraIndex != null) {
|
||||
await widget.selectCamera(_wideCameraIndex!, true);
|
||||
}
|
||||
} else {
|
||||
final level = await widget.controller.getMinZoomLevel();
|
||||
widget.updateScaleFactor(level);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_android_volume_keydown/flutter_android_volume_keydown.dart';
|
||||
|
|
@ -224,14 +225,21 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
);
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
androidVolumeDownSub = FlutterAndroidVolumeKeydown.stream.listen((event) {
|
||||
if (widget.isVisible) {
|
||||
takePicture();
|
||||
} else {
|
||||
deInitVolumeControl();
|
||||
return;
|
||||
}
|
||||
});
|
||||
if ((await DeviceInfoPlugin().androidInfo).version.release == '9') {
|
||||
// MissingPluginException: MissingPluginException(No implementation found for method cancel on channel dart-tools.dev/flutter_…
|
||||
// Maybe this is the reason?
|
||||
return;
|
||||
} else {
|
||||
androidVolumeDownSub =
|
||||
FlutterAndroidVolumeKeydown.stream.listen((event) {
|
||||
if (widget.isVisible) {
|
||||
takePicture();
|
||||
} else {
|
||||
deInitVolumeControl();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class EmojiPickerBottom extends StatelessWidget {
|
|||
return SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: EdgeInsets.zero,
|
||||
height: 450,
|
||||
height: 480,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(32),
|
||||
|
|
@ -30,7 +30,7 @@ class EmojiPickerBottom extends StatelessWidget {
|
|||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.all(30),
|
||||
margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
|
|
@ -52,11 +52,6 @@ class EmojiPickerBottom extends StatelessWidget {
|
|||
config: Config(
|
||||
height: 400,
|
||||
locale: Localizations.localeOf(context),
|
||||
viewOrderConfig: const ViewOrderConfig(
|
||||
top: EmojiPickerItem.searchBar,
|
||||
// middle: EmojiPickerItem.emojiView,
|
||||
bottom: EmojiPickerItem.categoryBar,
|
||||
),
|
||||
emojiTextStyle:
|
||||
TextStyle(fontSize: 24 * (Platform.isIOS ? 1.2 : 1)),
|
||||
emojiViewConfig: EmojiViewConfig(
|
||||
|
|
@ -68,7 +63,7 @@ class EmojiPickerBottom extends StatelessWidget {
|
|||
),
|
||||
categoryViewConfig: CategoryViewConfig(
|
||||
backgroundColor: context.color.surfaceContainer,
|
||||
dividerColor: Colors.white,
|
||||
dividerColor: context.color.surfaceContainerHigh,
|
||||
indicatorColor: context.color.primary,
|
||||
iconColorSelected: context.color.primary,
|
||||
iconColor: context.color.secondary,
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ class _UserListItem extends State<GroupListItem> {
|
|||
_previewMessages,
|
||||
_previewMediaFiles,
|
||||
lastReaction: _lastReaction,
|
||||
group: widget.group,
|
||||
),
|
||||
const Text('•'),
|
||||
const SizedBox(width: 5),
|
||||
|
|
|
|||
|
|
@ -35,11 +35,13 @@ class _LastMessageTimeState extends State<LastMessageTime> {
|
|||
lastMessageInSeconds =
|
||||
DateTime.now().difference(widget.dateTime!).inSeconds;
|
||||
}
|
||||
setState(() {
|
||||
if (lastMessageInSeconds < 0) {
|
||||
lastMessageInSeconds = 0;
|
||||
}
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (lastMessageInSeconds < 0) {
|
||||
lastMessageInSeconds = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:audio_waveforms/audio_waveforms.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||
|
|
@ -57,10 +59,13 @@ class _MessageInputState extends State<MessageInput> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textFieldController = TextEditingController();
|
||||
if (widget.group.draftMessage != null) {
|
||||
_textFieldController.text = widget.group.draftMessage!;
|
||||
}
|
||||
widget.textFieldFocus.addListener(_handleTextFocusChange);
|
||||
_initializeControllers();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -196,8 +201,15 @@ class _MessageInputState extends State<MessageInput> {
|
|||
keyboardType: TextInputType.multiline,
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
onChanged: (value) {
|
||||
onChanged: (value) async {
|
||||
setState(() {});
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
widget.group.groupId,
|
||||
GroupsCompanion(
|
||||
draftMessage:
|
||||
Value(_textFieldController.text),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSubmitted: (_) {
|
||||
_sendMessage();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:collection';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -45,6 +46,7 @@ class MessageSendStateIcon extends StatefulWidget {
|
|||
this.mediaFiles, {
|
||||
super.key,
|
||||
this.canBeReopened = false,
|
||||
this.group,
|
||||
this.lastReaction,
|
||||
this.mainAxisAlignment = MainAxisAlignment.end,
|
||||
});
|
||||
|
|
@ -53,6 +55,7 @@ class MessageSendStateIcon extends StatefulWidget {
|
|||
final Reaction? lastReaction;
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
final bool canBeReopened;
|
||||
final Group? group;
|
||||
|
||||
@override
|
||||
State<MessageSendStateIcon> createState() => _MessageSendStateIconState();
|
||||
|
|
@ -172,7 +175,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
};
|
||||
}
|
||||
if (mediaFile.uploadState == UploadState.preprocessing) {
|
||||
text = 'Wird verarbeitet';
|
||||
text = context.lang.inProcess;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +192,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
}
|
||||
|
||||
if (mediaFile != null) {
|
||||
if (mediaFile.reopenByContact) {
|
||||
if (message.mediaReopened) {
|
||||
icon = FaIcon(FontAwesomeIcons.repeat, size: 12, color: color);
|
||||
text = context.lang.messageReopened;
|
||||
}
|
||||
|
|
@ -221,38 +224,48 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
}
|
||||
}
|
||||
|
||||
if (widget.lastReaction != null &&
|
||||
!widget.messages.any((t) => t.openedAt == null)) {
|
||||
/// No messages are still open, so check if the reaction is the last message received.
|
||||
|
||||
if (!widget.messages
|
||||
.any((m) => m.createdAt.isAfter(widget.lastReaction!.createdAt))) {
|
||||
if (EmojiAnimation.animatedIcons
|
||||
.containsKey(widget.lastReaction!.emoji)) {
|
||||
icons = [
|
||||
SizedBox(
|
||||
height: 18,
|
||||
child: EmojiAnimation(emoji: widget.lastReaction!.emoji),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
icons = [
|
||||
SizedBox(
|
||||
height: 18,
|
||||
child: Center(
|
||||
child: Text(
|
||||
widget.lastReaction!.emoji,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
strutStyle: const StrutStyle(
|
||||
forceStrutHeight: true,
|
||||
height: 1.4,
|
||||
if (!widget.messages.any((t) => t.openedAt == null)) {
|
||||
if (widget.lastReaction != null) {
|
||||
/// No messages are still open, so check if the reaction is the last message received.
|
||||
if (!widget.messages
|
||||
.any((m) => m.createdAt.isAfter(widget.lastReaction!.createdAt))) {
|
||||
if (EmojiAnimation.animatedIcons
|
||||
.containsKey(widget.lastReaction!.emoji)) {
|
||||
icons = [
|
||||
SizedBox(
|
||||
height: 18,
|
||||
child: EmojiAnimation(emoji: widget.lastReaction!.emoji),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
icons = [
|
||||
SizedBox(
|
||||
height: 18,
|
||||
child: Center(
|
||||
child: Text(
|
||||
widget.lastReaction!.emoji,
|
||||
style: const TextStyle(fontSize: 15),
|
||||
strutStyle: const StrutStyle(
|
||||
forceStrutHeight: true,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
];
|
||||
}
|
||||
// Log.info("DISPLAY REACTION");
|
||||
}
|
||||
// Log.info("DISPLAY REACTION");
|
||||
}
|
||||
if (widget.group != null &&
|
||||
widget.group!.draftMessage != null &&
|
||||
widget.group!.draftMessage != '') {
|
||||
icons = [
|
||||
const FaIcon(FontAwesomeIcons.pen, size: 12, color: Colors.grey),
|
||||
];
|
||||
textWidget = Text(
|
||||
'${context.lang.draftMessage}: ${substringBy(widget.group!.draftMessage!, 10)}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:no_screenshot/no_screenshot.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart'
|
||||
show DownloadState, MediaType;
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -56,6 +58,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
bool imageSaved = false;
|
||||
bool imageSaving = false;
|
||||
bool displayTwonlyPresent = false;
|
||||
late String _currentMediaSender;
|
||||
final emojiKey = GlobalKey<EmojiFloatWidgetState>();
|
||||
|
||||
StreamSubscription<MediaFile?>? downloadStateListener;
|
||||
|
|
@ -69,6 +72,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentMediaSender = widget.group.groupName;
|
||||
|
||||
if (widget.initialMessage != null) {
|
||||
allMediaFiles = [widget.initialMessage!];
|
||||
|
|
@ -237,6 +241,15 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
displayTwonlyPresent = false;
|
||||
});
|
||||
|
||||
if (!widget.group.isDirectChat) {
|
||||
final sender =
|
||||
await twonlyDB.contactsDao.getContactById(currentMessage!.senderId!);
|
||||
if (sender != null) {
|
||||
_currentMediaSender =
|
||||
'${getContactDisplayName(sender)} (${widget.group.groupName})';
|
||||
}
|
||||
}
|
||||
|
||||
await notifyContactAboutOpeningMessage(
|
||||
currentMessage!.senderId!,
|
||||
[currentMessage!.messageId],
|
||||
|
|
@ -585,7 +598,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
left: showSendTextMessageInput ? 0 : null,
|
||||
right: showSendTextMessageInput ? 0 : 15,
|
||||
child: Text(
|
||||
widget.group.groupName,
|
||||
_currentMediaSender,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: showSendTextMessageInput ? 24 : 14,
|
||||
|
|
@ -630,6 +643,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
child: TextField(
|
||||
autofocus: true,
|
||||
controller: textMessageController,
|
||||
onChanged: (value) async {
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
widget.group.groupId,
|
||||
GroupsCompanion(
|
||||
draftMessage: Value(textMessageController.text),
|
||||
),
|
||||
);
|
||||
},
|
||||
onEditingComplete: () {
|
||||
setState(() {
|
||||
showSendTextMessageInput = false;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import 'package:twonly/globals.dart';
|
|||
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dart';
|
||||
import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart';
|
||||
|
||||
class DataAndStorageView extends StatefulWidget {
|
||||
const DataAndStorageView({super.key});
|
||||
|
|
@ -62,6 +64,36 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
|||
onChanged: (a) => toggleStoreInGallery(),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
context.lang.exportMemories,
|
||||
),
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return const ExportMediaView();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
context.lang.importMemories,
|
||||
),
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) {
|
||||
return const ImportMediaView();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: Text(
|
||||
|
|
|
|||
175
lib/src/views/settings/data_and_storage/export_media.view.dart
Normal file
175
lib/src/views/settings/data_and_storage/export_media.view.dart
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
|
||||
class ExportMediaView extends StatefulWidget {
|
||||
const ExportMediaView({super.key});
|
||||
|
||||
@override
|
||||
State<ExportMediaView> createState() => _ExportMediaViewState();
|
||||
}
|
||||
|
||||
class _ExportMediaViewState extends State<ExportMediaView> {
|
||||
double _progress = 0;
|
||||
String? _status;
|
||||
File? _zipFile;
|
||||
bool _isZipping = false;
|
||||
bool _zipSaved = false;
|
||||
|
||||
Future<Directory> _mediaFolder() async {
|
||||
final dir = MediaFileService.buildDirectoryPath(
|
||||
'stored',
|
||||
await getApplicationSupportDirectory(),
|
||||
);
|
||||
if (!dir.existsSync()) await dir.create(recursive: true);
|
||||
return dir;
|
||||
}
|
||||
|
||||
Future<void> _createZipFromMediaFolder() async {
|
||||
setState(() {
|
||||
_isZipping = true;
|
||||
_progress = 0.0;
|
||||
_status = 'Preparing...';
|
||||
_zipFile = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final folder = await _mediaFolder();
|
||||
final allFiles =
|
||||
folder.listSync(recursive: true).whereType<File>().toList();
|
||||
|
||||
final mediaFiles = allFiles.where((f) {
|
||||
final name = p.basename(f.path).toLowerCase();
|
||||
if (name.contains('thumbnail')) return false;
|
||||
return true;
|
||||
}).toList();
|
||||
|
||||
if (mediaFiles.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'No memories found.';
|
||||
_isZipping = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// compute total bytes for progress
|
||||
var totalBytes = 0;
|
||||
for (final f in mediaFiles) {
|
||||
totalBytes += await f.length();
|
||||
}
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final zipPath = p.join(
|
||||
tempDir.path,
|
||||
'memories.zip',
|
||||
);
|
||||
final encoder = ZipFileEncoder()..create(zipPath);
|
||||
|
||||
var processedBytes = 0;
|
||||
for (final f in mediaFiles) {
|
||||
final relative = p.relative(f.path, from: folder.path);
|
||||
setState(() {
|
||||
_status = 'Adding $relative';
|
||||
});
|
||||
|
||||
// ZipFileEncoder doesn't give per-file progress; update after adding.
|
||||
await encoder.addFile(f, relative);
|
||||
|
||||
processedBytes += await f.length();
|
||||
setState(() {
|
||||
_progress = totalBytes > 0 ? processedBytes / totalBytes : 0.0;
|
||||
});
|
||||
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 10),
|
||||
);
|
||||
}
|
||||
|
||||
await encoder.close();
|
||||
|
||||
setState(() {
|
||||
_zipFile = File(zipPath);
|
||||
_status = 'ZIP created: ${p.basename(zipPath)}';
|
||||
_progress = 1.0;
|
||||
_isZipping = false;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Error: $e';
|
||||
_isZipping = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveZip() async {
|
||||
if (_zipFile == null) return;
|
||||
try {
|
||||
final outputFile = await FilePicker.platform.saveFile(
|
||||
dialogTitle: 'Save your memories to desired location',
|
||||
fileName: p.basename(_zipFile!.path),
|
||||
bytes: _zipFile!.readAsBytesSync(),
|
||||
);
|
||||
if (outputFile == null) return;
|
||||
_zipSaved = true;
|
||||
_status = 'ZIP stored: ${p.basename(_zipFile!.path)}';
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
setState(() => _status = 'Save failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Export memories'),
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'Here, you can export all you memories.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_isZipping || _zipFile != null)
|
||||
LinearProgressIndicator(
|
||||
value: _isZipping ? _progress : (_zipFile != null ? 1.0 : 0.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_status != null)
|
||||
Text(
|
||||
_status!,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_zipFile == null)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.archive),
|
||||
label: Text(
|
||||
_isZipping ? 'Zipping...' : 'Create ZIP from mediafiles',
|
||||
),
|
||||
onPressed: _isZipping ? null : _createZipFromMediaFolder,
|
||||
)
|
||||
else if (!_zipSaved)
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.save_alt),
|
||||
label: const Text('Save ZIP'),
|
||||
onPressed: (_zipFile != null && !_isZipping) ? _saveZip : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
192
lib/src/views/settings/data_and_storage/import_media.view.dart
Normal file
192
lib/src/views/settings/data_and_storage/import_media.view.dart
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import 'dart:io';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
|
||||
class ImportMediaView extends StatefulWidget {
|
||||
const ImportMediaView({super.key});
|
||||
|
||||
@override
|
||||
State<ImportMediaView> createState() => _ImportMediaViewState();
|
||||
}
|
||||
|
||||
class _ImportMediaViewState extends State<ImportMediaView> {
|
||||
double _progress = 0;
|
||||
String? _status;
|
||||
File? _zipFile;
|
||||
bool _isProcessing = false;
|
||||
|
||||
Future<void> _pickAndImportZip() async {
|
||||
setState(() {
|
||||
_status = null;
|
||||
_progress = 0;
|
||||
_zipFile = null;
|
||||
_isProcessing = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['zip'],
|
||||
);
|
||||
|
||||
if (result == null || result.files.isEmpty) {
|
||||
setState(() {
|
||||
_status = 'No file selected.';
|
||||
_isProcessing = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final pickedPath = result.files.single.path;
|
||||
if (pickedPath == null) {
|
||||
setState(() {
|
||||
_status = 'Selected file has no path.';
|
||||
_isProcessing = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final pickedFile = File(pickedPath);
|
||||
if (!pickedFile.existsSync()) {
|
||||
setState(() {
|
||||
_status = 'Selected file does not exist.';
|
||||
_isProcessing = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_zipFile = pickedFile;
|
||||
_status = 'Selected ${p.basename(pickedPath)}';
|
||||
});
|
||||
|
||||
await _extractZipToMediaFolder(pickedFile);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Error: $e';
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _extractZipToMediaFolder(File zipFile) async {
|
||||
setState(() {
|
||||
_status = 'Reading archive...';
|
||||
_progress = 0;
|
||||
});
|
||||
|
||||
try {
|
||||
// Read zip bytes and decode
|
||||
final bytes = await zipFile.readAsBytes();
|
||||
final archive = ZipDecoder().decodeBytes(bytes);
|
||||
|
||||
// Optionally: compute total entries to show progress
|
||||
final entries = archive.where((e) => e.isFile).toList();
|
||||
final total = entries.length;
|
||||
var processed = 0;
|
||||
|
||||
for (final file in entries) {
|
||||
if (!file.isFile || file.isSymbolicLink) continue;
|
||||
|
||||
final extSplit = file.name.split('.');
|
||||
if (extSplit.isEmpty) continue;
|
||||
final ext = extSplit.last;
|
||||
|
||||
late MediaType type;
|
||||
switch (ext) {
|
||||
case 'webp':
|
||||
type = MediaType.image;
|
||||
case 'mp4':
|
||||
type = MediaType.video;
|
||||
case 'gif':
|
||||
type = MediaType.gif;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||
MediaFilesCompanion(
|
||||
type: Value(type),
|
||||
createdAt: Value(file.lastModDateTime),
|
||||
stored: const Value(true),
|
||||
),
|
||||
);
|
||||
final mediaService = await MediaFileService.fromMedia(mediaFile!);
|
||||
await mediaService.storedPath.writeAsBytes(file.content);
|
||||
|
||||
processed++;
|
||||
setState(() {
|
||||
_progress = total > 0 ? processed / total : 0;
|
||||
_status = 'Imported ${file.name}';
|
||||
});
|
||||
|
||||
// allow UI to update for large archives
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_status = 'Import complete. ${entries.length} entries processed.';
|
||||
_isProcessing = false;
|
||||
_progress = 1;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = 'Extraction failed: $e';
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Import memories'),
|
||||
),
|
||||
body: Container(
|
||||
margin: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'Here, you can import exported memories.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_isProcessing || _zipFile != null)
|
||||
LinearProgressIndicator(
|
||||
value:
|
||||
_isProcessing ? _progress : (_zipFile != null ? 1.0 : 0.0),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_status != null)
|
||||
Text(
|
||||
_status!,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.file_upload),
|
||||
label: Text(
|
||||
_isProcessing
|
||||
? 'Processing...'
|
||||
: 'Select memories.zip to import',
|
||||
),
|
||||
onPressed: _isProcessing ? null : _pickAndImportZip,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
|||
import 'package:avatar_maker/avatar_maker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
|
@ -77,69 +78,112 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool?> _showBackDialog() {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
context.lang.avatarSaveChanges,
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
child: Text(context.lang.avatarSaveChangesStore),
|
||||
onPressed: () async {
|
||||
await storeAvatarAndExit();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(context.lang.avatarSaveChangesDiscard),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> storeAvatarAndExit() async {
|
||||
await _avatarMakerController.saveAvatarSVG();
|
||||
final json = _avatarMakerController.getJsonOptionsSync();
|
||||
final svg = _avatarMakerController.getAvatarSVGSync();
|
||||
await updateUserAvatar(json, svg);
|
||||
if (mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.settingsProfileCustomizeAvatar),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: AvatarMakerAvatar(
|
||||
radius: 130,
|
||||
backgroundColor: Colors.transparent,
|
||||
controller: _avatarMakerController,
|
||||
return PopScope<bool?>(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, bool? result) async {
|
||||
if (didPop) return;
|
||||
if (_avatarMakerController.getJsonOptionsSync() != gUser.avatarJson) {
|
||||
// there where changes
|
||||
final shouldPop = await _showBackDialog() ?? false;
|
||||
if (context.mounted && shouldPop) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.settingsProfileCustomizeAvatar),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: AvatarMakerAvatar(
|
||||
radius: 130,
|
||||
backgroundColor: Colors.transparent,
|
||||
controller: _avatarMakerController,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const FaIcon(FontAwesomeIcons.floppyDisk),
|
||||
onPressed: () async {
|
||||
await _avatarMakerController.saveAvatarSVG();
|
||||
final json =
|
||||
_avatarMakerController.getJsonOptionsSync();
|
||||
final svg = _avatarMakerController.getAvatarSVGSync();
|
||||
await updateUserAvatar(json, svg);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const FaIcon(FontAwesomeIcons.shuffle),
|
||||
onPressed:
|
||||
_avatarMakerController.randomizedSelectedOptions,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(FontAwesomeIcons.rotateLeft),
|
||||
onLongPress: () async {
|
||||
await PersistentAvatarMakerController
|
||||
.clearAvatarMaker();
|
||||
await _avatarMakerController.restoreState();
|
||||
},
|
||||
onPressed: _avatarMakerController.restoreState,
|
||||
),
|
||||
],
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const FaIcon(FontAwesomeIcons.floppyDisk),
|
||||
onPressed: storeAvatarAndExit,
|
||||
),
|
||||
IconButton(
|
||||
icon: const FaIcon(FontAwesomeIcons.shuffle),
|
||||
onPressed:
|
||||
_avatarMakerController.randomizedSelectedOptions,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(FontAwesomeIcons.rotateLeft),
|
||||
onLongPress: () async {
|
||||
await PersistentAvatarMakerController
|
||||
.clearAvatarMaker();
|
||||
await _avatarMakerController.restoreState();
|
||||
},
|
||||
onPressed: _avatarMakerController.restoreState,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 30),
|
||||
child: AvatarMakerCustomizer(
|
||||
scaffoldWidth:
|
||||
min(600, MediaQuery.of(context).size.width * 0.85),
|
||||
theme: getAvatarMakerTheme(context),
|
||||
controller: _avatarMakerController,
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 30),
|
||||
child: AvatarMakerCustomizer(
|
||||
scaffoldWidth:
|
||||
min(600, MediaQuery.of(context).size.width * 0.85),
|
||||
theme: getAvatarMakerTheme(context),
|
||||
controller: _avatarMakerController,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
10
pubspec.lock
10
pubspec.lock
|
|
@ -34,7 +34,7 @@ packages:
|
|||
source: hosted
|
||||
version: "8.4.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: archive
|
||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
||||
|
|
@ -442,6 +442,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: f8f4ea435f791ab1f817b4e338ed958cb3d04ba43d6736ffc39958d950754967
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.6"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
|||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.0.67+67
|
||||
version: 0.0.69+69
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
|
||||
dependencies:
|
||||
archive: ^4.0.7
|
||||
audio_waveforms: ^1.3.0
|
||||
avatar_maker: ^0.4.0
|
||||
background_downloader: ^9.2.2
|
||||
|
|
@ -23,6 +24,7 @@ dependencies:
|
|||
drift_flutter: ^0.2.4
|
||||
emoji_picker_flutter: ^4.3.0
|
||||
ffmpeg_kit_flutter_new: ^4.1.0
|
||||
file_picker: ^10.3.6
|
||||
firebase_core: ^4.2.0
|
||||
firebase_messaging: ^16.0.3
|
||||
fixnum: ^1.1.1
|
||||
|
|
@ -109,6 +111,7 @@ flutter_launcher_icons:
|
|||
ios: false
|
||||
image_path: "assets/images/logo.png"
|
||||
adaptive_icon_foreground: "assets/images/logo.png"
|
||||
adaptive_icon_monochrome: "assets/images/logo.png"
|
||||
min_sdk_android: 21 # android min sdk min:16, default 21
|
||||
remove_alpha_ios: true
|
||||
adaptive_icon_background: "#FF57CC99"
|
||||
|
|
|
|||
26
test/drift/twonly_db/generated/schema.dart
Normal file
26
test/drift/twonly_db/generated/schema.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// dart format width=80
|
||||
// GENERATED CODE, DO NOT EDIT BY HAND.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/internal/migrations.dart';
|
||||
import 'schema_v1.dart' as v1;
|
||||
import 'schema_v2.dart' as v2;
|
||||
import 'schema_v3.dart' as v3;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
|
||||
switch (version) {
|
||||
case 1:
|
||||
return v1.DatabaseAtV1(db);
|
||||
case 2:
|
||||
return v2.DatabaseAtV2(db);
|
||||
case 3:
|
||||
return v3.DatabaseAtV3(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2, 3];
|
||||
}
|
||||
6384
test/drift/twonly_db/generated/schema_v1.dart
Normal file
6384
test/drift/twonly_db/generated/schema_v1.dart
Normal file
File diff suppressed because it is too large
Load diff
6384
test/drift/twonly_db/generated/schema_v2.dart
Normal file
6384
test/drift/twonly_db/generated/schema_v2.dart
Normal file
File diff suppressed because it is too large
Load diff
6420
test/drift/twonly_db/generated/schema_v3.dart
Normal file
6420
test/drift/twonly_db/generated/schema_v3.dart
Normal file
File diff suppressed because it is too large
Load diff
172
test/drift/twonly_db/migration_test.dart
Normal file
172
test/drift/twonly_db/migration_test.dart
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
// dart format width=80
|
||||
// ignore_for_file: unused_local_variable, unused_import
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_dev/api/migrations_native.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'generated/schema.dart';
|
||||
|
||||
import 'generated/schema_v1.dart' as v1;
|
||||
import 'generated/schema_v2.dart' as v2;
|
||||
|
||||
void main() {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
late SchemaVerifier verifier;
|
||||
|
||||
setUpAll(() {
|
||||
verifier = SchemaVerifier(GeneratedHelper());
|
||||
});
|
||||
|
||||
group('simple database migrations', () {
|
||||
// These simple tests verify all possible schema updates with a simple (no
|
||||
// data) migration. This is a quick way to ensure that written database
|
||||
// migrations properly alter the schema.
|
||||
const versions = GeneratedHelper.versions;
|
||||
for (final (i, fromVersion) in versions.indexed) {
|
||||
group('from $fromVersion', () {
|
||||
for (final toVersion in versions.skip(i + 1)) {
|
||||
test('to $toVersion', () async {
|
||||
final schema = await verifier.schemaAt(fromVersion);
|
||||
final db = TwonlyDB(schema.newConnection());
|
||||
await verifier.migrateAndValidate(db, toVersion);
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// The following template shows how to write tests ensuring your migrations
|
||||
// preserve existing data.
|
||||
// Testing this can be useful for migrations that change existing columns
|
||||
// (e.g. by alterating their type or constraints). Migrations that only add
|
||||
// tables or columns typically don't need these advanced tests. For more
|
||||
// information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity
|
||||
// TODO: This generated template shows how these tests could be written. Adopt
|
||||
// it to your own needs when testing migrations with data integrity.
|
||||
test('migration from v1 to v2 does not corrupt data', () async {
|
||||
// Add data to insert into the old database, and the expected rows after the
|
||||
// migration.
|
||||
// TODO: Fill these lists
|
||||
final oldContactsData = <v1.ContactsData>[];
|
||||
final expectedNewContactsData = <v2.ContactsData>[];
|
||||
|
||||
final oldGroupsData = <v1.GroupsData>[];
|
||||
final expectedNewGroupsData = <v2.GroupsData>[];
|
||||
|
||||
final oldMediaFilesData = <v1.MediaFilesData>[];
|
||||
final expectedNewMediaFilesData = <v2.MediaFilesData>[];
|
||||
|
||||
final oldMessagesData = <v1.MessagesData>[];
|
||||
final expectedNewMessagesData = <v2.MessagesData>[];
|
||||
|
||||
final oldMessageHistoriesData = <v1.MessageHistoriesData>[];
|
||||
final expectedNewMessageHistoriesData = <v2.MessageHistoriesData>[];
|
||||
|
||||
final oldReactionsData = <v1.ReactionsData>[];
|
||||
final expectedNewReactionsData = <v2.ReactionsData>[];
|
||||
|
||||
final oldGroupMembersData = <v1.GroupMembersData>[];
|
||||
final expectedNewGroupMembersData = <v2.GroupMembersData>[];
|
||||
|
||||
final oldReceiptsData = <v1.ReceiptsData>[];
|
||||
final expectedNewReceiptsData = <v2.ReceiptsData>[];
|
||||
|
||||
final oldReceivedReceiptsData = <v1.ReceivedReceiptsData>[];
|
||||
final expectedNewReceivedReceiptsData = <v2.ReceivedReceiptsData>[];
|
||||
|
||||
final oldSignalIdentityKeyStoresData = <v1.SignalIdentityKeyStoresData>[];
|
||||
final expectedNewSignalIdentityKeyStoresData =
|
||||
<v2.SignalIdentityKeyStoresData>[];
|
||||
|
||||
final oldSignalPreKeyStoresData = <v1.SignalPreKeyStoresData>[];
|
||||
final expectedNewSignalPreKeyStoresData = <v2.SignalPreKeyStoresData>[];
|
||||
|
||||
final oldSignalSenderKeyStoresData = <v1.SignalSenderKeyStoresData>[];
|
||||
final expectedNewSignalSenderKeyStoresData =
|
||||
<v2.SignalSenderKeyStoresData>[];
|
||||
|
||||
final oldSignalSessionStoresData = <v1.SignalSessionStoresData>[];
|
||||
final expectedNewSignalSessionStoresData = <v2.SignalSessionStoresData>[];
|
||||
|
||||
final oldSignalContactPreKeysData = <v1.SignalContactPreKeysData>[];
|
||||
final expectedNewSignalContactPreKeysData = <v2.SignalContactPreKeysData>[];
|
||||
|
||||
final oldSignalContactSignedPreKeysData =
|
||||
<v1.SignalContactSignedPreKeysData>[];
|
||||
final expectedNewSignalContactSignedPreKeysData =
|
||||
<v2.SignalContactSignedPreKeysData>[];
|
||||
|
||||
final oldMessageActionsData = <v1.MessageActionsData>[];
|
||||
final expectedNewMessageActionsData = <v2.MessageActionsData>[];
|
||||
|
||||
final oldGroupHistoriesData = <v1.GroupHistoriesData>[];
|
||||
final expectedNewGroupHistoriesData = <v2.GroupHistoriesData>[];
|
||||
|
||||
await verifier.testWithDataIntegrity(
|
||||
oldVersion: 1,
|
||||
newVersion: 2,
|
||||
createOld: v1.DatabaseAtV1.new,
|
||||
createNew: v2.DatabaseAtV2.new,
|
||||
openTestedDatabase: TwonlyDB.new,
|
||||
createItems: (batch, oldDb) {
|
||||
batch.insertAll(oldDb.contacts, oldContactsData);
|
||||
batch.insertAll(oldDb.groups, oldGroupsData);
|
||||
batch.insertAll(oldDb.mediaFiles, oldMediaFilesData);
|
||||
batch.insertAll(oldDb.messages, oldMessagesData);
|
||||
batch.insertAll(oldDb.messageHistories, oldMessageHistoriesData);
|
||||
batch.insertAll(oldDb.reactions, oldReactionsData);
|
||||
batch.insertAll(oldDb.groupMembers, oldGroupMembersData);
|
||||
batch.insertAll(oldDb.receipts, oldReceiptsData);
|
||||
batch.insertAll(oldDb.receivedReceipts, oldReceivedReceiptsData);
|
||||
batch.insertAll(
|
||||
oldDb.signalIdentityKeyStores, oldSignalIdentityKeyStoresData);
|
||||
batch.insertAll(oldDb.signalPreKeyStores, oldSignalPreKeyStoresData);
|
||||
batch.insertAll(
|
||||
oldDb.signalSenderKeyStores, oldSignalSenderKeyStoresData);
|
||||
batch.insertAll(oldDb.signalSessionStores, oldSignalSessionStoresData);
|
||||
batch.insertAll(
|
||||
oldDb.signalContactPreKeys, oldSignalContactPreKeysData);
|
||||
batch.insertAll(oldDb.signalContactSignedPreKeys,
|
||||
oldSignalContactSignedPreKeysData);
|
||||
batch.insertAll(oldDb.messageActions, oldMessageActionsData);
|
||||
batch.insertAll(oldDb.groupHistories, oldGroupHistoriesData);
|
||||
},
|
||||
validateItems: (newDb) async {
|
||||
expect(
|
||||
expectedNewContactsData, await newDb.select(newDb.contacts).get());
|
||||
expect(expectedNewGroupsData, await newDb.select(newDb.groups).get());
|
||||
expect(expectedNewMediaFilesData,
|
||||
await newDb.select(newDb.mediaFiles).get());
|
||||
expect(
|
||||
expectedNewMessagesData, await newDb.select(newDb.messages).get());
|
||||
expect(expectedNewMessageHistoriesData,
|
||||
await newDb.select(newDb.messageHistories).get());
|
||||
expect(expectedNewReactionsData,
|
||||
await newDb.select(newDb.reactions).get());
|
||||
expect(expectedNewGroupMembersData,
|
||||
await newDb.select(newDb.groupMembers).get());
|
||||
expect(
|
||||
expectedNewReceiptsData, await newDb.select(newDb.receipts).get());
|
||||
expect(expectedNewReceivedReceiptsData,
|
||||
await newDb.select(newDb.receivedReceipts).get());
|
||||
expect(expectedNewSignalIdentityKeyStoresData,
|
||||
await newDb.select(newDb.signalIdentityKeyStores).get());
|
||||
expect(expectedNewSignalPreKeyStoresData,
|
||||
await newDb.select(newDb.signalPreKeyStores).get());
|
||||
expect(expectedNewSignalSenderKeyStoresData,
|
||||
await newDb.select(newDb.signalSenderKeyStores).get());
|
||||
expect(expectedNewSignalSessionStoresData,
|
||||
await newDb.select(newDb.signalSessionStores).get());
|
||||
expect(expectedNewSignalContactPreKeysData,
|
||||
await newDb.select(newDb.signalContactPreKeys).get());
|
||||
expect(expectedNewSignalContactSignedPreKeysData,
|
||||
await newDb.select(newDb.signalContactSignedPreKeys).get());
|
||||
expect(expectedNewMessageActionsData,
|
||||
await newDb.select(newDb.messageActions).get());
|
||||
expect(expectedNewGroupHistoriesData,
|
||||
await newDb.select(newDb.groupHistories).get());
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue