diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47c024e..d21b202 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,11 @@
# Changelog
-## 0.0.94
+## 0.0.96
+Feature: Show link in chat if the saved media file contains one
+Improve: Verification badge for groups
+Improve: Huge reduction in app size
+Fix: Crash on older devices when compressing a video
Fix: Problem with decrypting messages fixed
## 0.0.93
diff --git a/assets/icons/verified_badge_yellow.svg b/assets/icons/verified_badge_yellow.svg
deleted file mode 100644
index 29ee80a..0000000
--- a/assets/icons/verified_badge_yellow.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 3b9e45f..ae7f32b 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -46,11 +46,6 @@ PODS:
- SwiftyGif
- emoji_picker_flutter (0.0.1):
- Flutter
- - ffmpeg_kit_flutter_new (1.0.0):
- - ffmpeg_kit_flutter_new/full-gpl (= 1.0.0)
- - Flutter
- - ffmpeg_kit_flutter_new/full-gpl (1.0.0):
- - Flutter
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
@@ -278,6 +273,8 @@ PODS:
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
+ - pro_video_editor (0.0.1):
+ - Flutter
- PromisesObjC (2.4.0)
- restart_app (0.0.1):
- Flutter
@@ -329,6 +326,8 @@ PODS:
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter
+ - video_compress (0.3.0):
+ - Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -342,7 +341,6 @@ DEPENDENCIES:
- cryptography_flutter_plus (from `.symlinks/plugins/cryptography_flutter_plus/ios`)
- 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`)
@@ -368,6 +366,7 @@ DEPENDENCIES:
- no_screenshot (from `.symlinks/plugins/no_screenshot/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
+ - pro_video_editor (from `.symlinks/plugins/pro_video_editor/ios`)
- restart_app (from `.symlinks/plugins/restart_app/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -376,6 +375,7 @@ DEPENDENCIES:
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- SwiftProtobuf
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+ - video_compress (from `.symlinks/plugins/video_compress/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
SPEC REPOS:
@@ -428,8 +428,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios"
emoji_picker_flutter:
: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:
@@ -470,6 +468,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
+ pro_video_editor:
+ :path: ".symlinks/plugins/pro_video_editor/ios"
restart_app:
:path: ".symlinks/plugins/restart_app/ios"
sentry_flutter:
@@ -484,6 +484,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
+ video_compress:
+ :path: ".symlinks/plugins/video_compress/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
@@ -498,7 +500,6 @@ SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
- ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d
firebase_core: ee30637e6744af8e0c12a6a1e8a9718506ec2398
@@ -540,6 +541,7 @@ SPEC CHECKSUMS:
no_screenshot: 5e345998c43ffcad5d6834f249590483fcc037bd
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
+ pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf
SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477
@@ -554,6 +556,7 @@ SPEC CHECKSUMS:
SwiftProtobuf: c901f00a3e125dc33cac9b16824da85682ee47da
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
+ video_compress: f2133a07762889d67f0711ac831faa26f956980e
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
PODFILE CHECKSUM: ae041999f13ba7b2285ff9ad9bc688ed647bbcb7
diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart
index 1e41efc..db0b2d6 100644
--- a/lib/src/database/daos/messages.dao.dart
+++ b/lib/src/database/daos/messages.dao.dart
@@ -212,31 +212,40 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin {
);
}
- Future handleMessageOpened(
+ Future handleMessagesOpened(
int contactId,
- String messageId,
+ List messageIds,
DateTime timestamp,
) async {
- await into(messageActions).insertOnConflictUpdate(
- MessageActionsCompanion(
- messageId: Value(messageId),
- contactId: Value(contactId),
- type: const Value(MessageActionType.openedAt),
- actionAt: Value(timestamp),
- ),
- );
- // Directly show as message opened as soon as one person has opened it
- final openedByAll =
- await haveAllMembers(messageId, MessageActionType.openedAt)
- ? clock.now()
- : null;
- await twonlyDB.messagesDao.updateMessageId(
- messageId,
- MessagesCompanion(
- openedAt: Value(clock.now()),
- openedByAll: Value(openedByAll),
- ),
- );
+ await batch((batch) async {
+ for (final messageId in messageIds) {
+ batch.insert(
+ messageActions,
+ MessageActionsCompanion(
+ messageId: Value(messageId),
+ contactId: Value(contactId),
+ type: const Value(MessageActionType.openedAt),
+ actionAt: Value(timestamp),
+ ),
+ mode: InsertMode.insertOrReplace,
+ );
+ }
+
+ for (final messageId in messageIds) {
+ final isOpenedByAll =
+ await haveAllMembers(messageId, MessageActionType.openedAt);
+ final now = clock.now();
+
+ batch.update(
+ twonlyDB.messages,
+ MessagesCompanion(
+ openedAt: Value(now),
+ openedByAll: Value(isOpenedByAll ? now : null),
+ ),
+ where: (tbl) => tbl.messageId.equals(messageId),
+ );
+ }
+ });
}
Future handleMessageAckByServer(
@@ -342,38 +351,6 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin {
.getSingleOrNull();
}
- Stream>> watchLastOpenedMessagePerContact(
- String groupId,
- ) {
- const sql = '''
- SELECT m.*, c.*
- FROM (
- SELECT ma.contact_id, ma.message_id,
- ROW_NUMBER() OVER (PARTITION BY ma.contact_id
- ORDER BY ma.action_at DESC, ma.message_id DESC) AS rn
- FROM message_actions ma
- WHERE ma.type = 'openedAt'
- ) last_open
- JOIN messages m ON m.message_id = last_open.message_id
- JOIN contacts c ON c.user_id = last_open.contact_id
- WHERE last_open.rn = 1 AND m.group_id = ?;
- ''';
-
- return customSelect(
- sql,
- variables: [Variable.withString(groupId)],
- readsFrom: {messages, messageActions, contacts},
- ).watch().map((rows) async {
- final res = <(Message, Contact)>[];
- for (final row in rows) {
- final message = await messages.mapFromRow(row);
- final contact = await contacts.mapFromRow(row);
- res.add((message, contact));
- }
- return res;
- });
- }
-
Future deleteMessagesById(String messageId) {
return (delete(messages)..where((t) => t.messageId.equals(messageId))).go();
}
diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.dart
index 9945264..39bd078 100644
--- a/lib/src/model/json/userdata.dart
+++ b/lib/src/model/json/userdata.dart
@@ -31,9 +31,6 @@ class UserData {
@JsonKey(defaultValue: false)
bool isDeveloper = false;
- @JsonKey(defaultValue: false)
- bool disableVideoCompression = false;
-
@JsonKey(defaultValue: 0)
int deviceId = 0;
diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.g.dart
index 3dc2fbb..83ddff8 100644
--- a/lib/src/model/json/userdata.g.dart
+++ b/lib/src/model/json/userdata.g.dart
@@ -17,8 +17,6 @@ UserData _$UserDataFromJson(Map json) => UserData(
..appVersion = (json['appVersion'] as num?)?.toInt() ?? 0
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
..isDeveloper = json['isDeveloper'] as bool? ?? false
- ..disableVideoCompression =
- json['disableVideoCompression'] as bool? ?? false
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String?
..lastImageSend = json['lastImageSend'] == null
@@ -95,7 +93,6 @@ Map _$UserDataToJson(UserData instance) => {
'appVersion': instance.appVersion,
'avatarCounter': instance.avatarCounter,
'isDeveloper': instance.isDeveloper,
- 'disableVideoCompression': instance.disableVideoCompression,
'deviceId': instance.deviceId,
'subscriptionPlan': instance.subscriptionPlan,
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
diff --git a/lib/src/model/protobuf/client/generated/messages.pbenum.dart b/lib/src/model/protobuf/client/generated/messages.pbenum.dart
index 8277780..a8d9e86 100644
--- a/lib/src/model/protobuf/client/generated/messages.pbenum.dart
+++ b/lib/src/model/protobuf/client/generated/messages.pbenum.dart
@@ -76,17 +76,21 @@ class EncryptedContent_ErrorMessages_Type extends $pb.ProtobufEnum {
static const EncryptedContent_ErrorMessages_Type UNKNOWN_MESSAGE_TYPE =
EncryptedContent_ErrorMessages_Type._(
2, _omitEnumNames ? '' : 'UNKNOWN_MESSAGE_TYPE');
+ static const EncryptedContent_ErrorMessages_Type SESSION_OUT_OF_SYNC =
+ EncryptedContent_ErrorMessages_Type._(
+ 3, _omitEnumNames ? '' : 'SESSION_OUT_OF_SYNC');
static const $core.List values =
[
ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
UNKNOWN_MESSAGE_TYPE,
+ SESSION_OUT_OF_SYNC,
];
- static final $core.Map<$core.int, EncryptedContent_ErrorMessages_Type>
- _byValue = $pb.ProtobufEnum.initByValue(values);
+ static final $core.List _byValue =
+ $pb.ProtobufEnum.$_initByValueList(values, 3);
static EncryptedContent_ErrorMessages_Type? valueOf($core.int value) =>
- _byValue[value];
+ value < 0 || value >= _byValue.length ? null : _byValue[value];
const EncryptedContent_ErrorMessages_Type._(super.value, super.name);
}
diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart
index ac60f8f..a8a1fa3 100644
--- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart
+++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart
@@ -395,6 +395,7 @@ const EncryptedContent_ErrorMessages_Type$json = {
'2': [
{'1': 'ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD', '2': 0},
{'1': 'UNKNOWN_MESSAGE_TYPE', '2': 2},
+ {'1': 'SESSION_OUT_OF_SYNC', '2': 3},
],
};
@@ -863,67 +864,68 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'gBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz'
'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgAS'
'gLMicuRW5jcnlwdGVkQ29udGVudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIEVIVYWRkaXRpb25h'
- 'bERhdGFNZXNzYWdliAEBGtcBCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeX'
+ 'bERhdGFNZXNzYWdliAEBGvABCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeX'
'B0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRf'
- 'aWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQiXgoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX0'
+ 'aWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQidwoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX0'
'1FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVT'
- 'U0FHRV9UWVBFEAIaUQoLR3JvdXBDcmVhdGUSGgoIc3RhdGVLZXkYAyABKAxSCHN0YXRlS2V5Ei'
- 'YKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRozCglHcm91cEpvaW4SJgoO'
- 'Z3JvdXBQdWJsaWNLZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibG'
- 'ljS2V5GrYCCgtHcm91cFVwZGF0ZRIoCg9ncm91cEFjdGlvblR5cGUYASABKAlSD2dyb3VwQWN0'
- 'aW9uVHlwZRIxChFhZmZlY3RlZENvbnRhY3RJZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZI'
- 'gBARInCgxuZXdHcm91cE5hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElMKIm5ld0RlbGV0'
- 'ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZn'
- 'Rlck1pbGxpc2Vjb25kc4gBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25ld0dyb3VwTmFt'
- 'ZUIlCiNfbmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcxqpAQoLVGV4dE1lc3NhZ2'
- 'USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoEdGV4dBgCIAEo'
- 'CVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZB'
- 'gEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQazgEKFUFkZGl0'
- 'aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2'
- 'FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJdGltZXN0YW1wEhIKBHR5cGUYAyABKAlSBHR5cGUS'
- 'OwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYBCABKAxIAFIVYWRkaXRpb25hbE1lc3NhZ2VEYX'
- 'RhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRpiCghSZWFjdGlvbhIoCg90YXJnZXRN'
- 'ZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1vamkSFg'
- 'oGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIk'
- 'LkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3'
- 'NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYXJnZXRNZXNz'
- 'YWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUg'
- 'R0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRF'
- 'EAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdG'
- 'V4dBrwBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQS'
- 'MAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaX'
- 'NwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vj'
- 'b25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbn'
- 'RpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlk'
- 'GAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG'
- '93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmI'
- 'AQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cH'
- 'Rpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNz'
- 'YWdlX2RhdGEYCyABKAxIBlIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUk'
- 'VVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIdChtf'
- 'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG'
- '9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0'
- 'aW9uTm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGqcBCgtNZWRpYVVwZGF0ZRI2Cg'
- 'R0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEigK'
- 'D3RhcmdldE1lc3NhZ2VJZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDAoIUkVPUE'
- 'VORUQQABIKCgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVl'
- 'c3QSOQoEdHlwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZV'
- 'IEdHlwZSIrCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhqeAgoN'
- 'Q29udGFjdFVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VX'
- 'BkYXRlLlR5cGVSBHR5cGUSNQoTYXZhdGFyU3ZnQ29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJT'
- 'dmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiUKC2Rpc3'
- 'BsYXlOYW1lGAQgASgJSAJSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoK'
- 'BlVQREFURRABQhYKFF9hdmF0YXJTdmdDb21wcmVzc2VkQgsKCV91c2VybmFtZUIOCgxfZGlzcG'
- 'xheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW50LlB1'
- 'c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgAS'
- 'gMSAFSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUS'
- 'CwoHUkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVhdGVkQX'
- 'QaqQEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1lQ291bnRlchI2ChZs'
- 'YXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh4KCm'
- 'Jlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmQSIAoLZm9yY2VVcGRhdGUYBCABKAhSC2ZvcmNl'
- 'VXBkYXRlQgoKCF9ncm91cElkQg8KDV9pc0RpcmVjdENoYXRCFwoVX3NlbmRlclByb2ZpbGVDb3'
- 'VudGVyQhAKDl9tZXNzYWdlVXBkYXRlQggKBl9tZWRpYUIOCgxfbWVkaWFVcGRhdGVCEAoOX2Nv'
- 'bnRhY3RVcGRhdGVCEQoPX2NvbnRhY3RSZXF1ZXN0QgwKCl9mbGFtZVN5bmNCCwoJX3B1c2hLZX'
- 'lzQgsKCV9yZWFjdGlvbkIOCgxfdGV4dE1lc3NhZ2VCDgoMX2dyb3VwQ3JlYXRlQgwKCl9ncm91'
- 'cEpvaW5CDgoMX2dyb3VwVXBkYXRlQhcKFV9yZXNlbmRHcm91cFB1YmxpY0tleUIRCg9fZXJyb3'
- 'JfbWVzc2FnZXNCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdl');
+ 'U0FHRV9UWVBFEAISFwoTU0VTU0lPTl9PVVRfT0ZfU1lOQxADGlEKC0dyb3VwQ3JlYXRlEhoKCH'
+ 'N0YXRlS2V5GAMgASgMUghzdGF0ZUtleRImCg5ncm91cFB1YmxpY0tleRgEIAEoDFIOZ3JvdXBQ'
+ 'dWJsaWNLZXkaMwoJR3JvdXBKb2luEiYKDmdyb3VwUHVibGljS2V5GAEgASgMUg5ncm91cFB1Ym'
+ 'xpY0tleRoWChRSZXNlbmRHcm91cFB1YmxpY0tleRq2AgoLR3JvdXBVcGRhdGUSKAoPZ3JvdXBB'
+ 'Y3Rpb25UeXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMQoRYWZmZWN0ZWRDb250YWN0SWQYAi'
+ 'ABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESJwoMbmV3R3JvdXBOYW1lGAMgASgJSAFSDG5l'
+ 'd0dyb3VwTmFtZYgBARJTCiJuZXdEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlzZWNvbmRzGAQgAS'
+ 'gDSAJSIm5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHOIAQFCFAoSX2FmZmVjdGVk'
+ 'Q29udGFjdElkQg8KDV9uZXdHcm91cE5hbWVCJQojX25ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaW'
+ 'xsaXNlY29uZHMaqQEKC1RleHRNZXNzYWdlEigKD3NlbmRlck1lc3NhZ2VJZBgBIAEoCVIPc2Vu'
+ 'ZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW'
+ '1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFCEQoP'
+ 'X3F1b3RlTWVzc2FnZUlkGs4BChVBZGRpdGlvbmFsRGF0YU1lc3NhZ2USKgoRc2VuZGVyX21lc3'
+ 'NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIcCgl0aW1lc3RhbXAYAiABKANSCXRpbWVz'
+ 'dGFtcBISCgR0eXBlGAMgASgJUgR0eXBlEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAQgAS'
+ 'gMSABSFWFkZGl0aW9uYWxNZXNzYWdlRGF0YYgBAUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2Rh'
+ 'dGEaYgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSW'
+ 'QSFAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGrcCCg1NZXNz'
+ 'YWdlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdG'
+ 'UuVHlwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlk'
+ 'iAEBEjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZX'
+ 'NzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRp'
+ 'bWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQh'
+ 'IKEF9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQa8AUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJ'
+ 'ZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW'
+ '50Lk1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANI'
+ 'AFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdG'
+ 'lvbhgEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRp'
+ 'bWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIpCg'
+ '1kb3dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktl'
+ 'eRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW'
+ '5jcnlwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5v'
+ 'bmNliAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZX'
+ 'NzYWdlRGF0YYgBASI+CgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxAC'
+ 'EgcKA0dJRhADEgkKBUFVRElPEARCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD1'
+ '9xdW90ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5f'
+ 'ZW5jcnlwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2'
+ 'VfZGF0YRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQu'
+ 'TWVkaWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE'
+ '1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElP'
+ 'Tl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb2'
+ '50ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoG'
+ 'UkVKRUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLk'
+ 'VuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0Nv'
+ 'bXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIA'
+ 'EoCUgBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgB'
+ 'ASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3'
+ 'NlZEILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEg'
+ 'ASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgAS'
+ 'gDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgD'
+ 'SAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2'
+ 'V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVy'
+ 'GAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbG'
+ 'FzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiAK'
+ 'C2ZvcmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3'
+ 'RDaGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVk'
+ 'aWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdE'
+ 'IMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdl'
+ 'Qg4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW'
+ '5kR3JvdXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFf'
+ 'bWVzc2FnZQ==');
diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto
index 062fc6f..7154bac 100644
--- a/lib/src/model/protobuf/client/messages.proto
+++ b/lib/src/model/protobuf/client/messages.proto
@@ -58,6 +58,7 @@ message EncryptedContent {
enum Type {
ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD = 0;
UNKNOWN_MESSAGE_TYPE = 2;
+ SESSION_OUT_OF_SYNC = 3;
}
Type type = 1;
string related_receipt_id = 2;
diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart
index d5259f0..598424e 100644
--- a/lib/src/services/api/client2client/errors.c2c.dart
+++ b/lib/src/services/api/client2client/errors.c2c.dart
@@ -3,14 +3,15 @@ import 'package:drift/drift.dart' show Value;
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
+import 'package:twonly/src/utils/log.dart';
Future handleErrorMessage(
int fromUserId,
EncryptedContent_ErrorMessages error,
) async {
+ Log.error('Got error from $fromUserId: $error');
+
switch (error.type) {
- case EncryptedContent_ErrorMessages_Type.UNKNOWN_MESSAGE_TYPE:
- break;
case EncryptedContent_ErrorMessages_Type
.ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD:
await twonlyDB.receiptsDao.updateReceiptWidthUserId(
@@ -25,5 +26,8 @@ Future handleErrorMessage(
requested: Value(true),
),
);
+ // ignore: no_default_cases
+ default:
+ break;
}
}
diff --git a/lib/src/services/api/client2client/messages.c2c.dart b/lib/src/services/api/client2client/messages.c2c.dart
index 8a50ac6..e193ed3 100644
--- a/lib/src/services/api/client2client/messages.c2c.dart
+++ b/lib/src/services/api/client2client/messages.c2c.dart
@@ -9,19 +9,17 @@ Future handleMessageUpdate(
) async {
switch (messageUpdate.type) {
case EncryptedContent_MessageUpdate_Type.OPENED:
- for (final targetMessageId in messageUpdate.multipleTargetMessageIds) {
- Log.info(
- 'Opened message $targetMessageId',
+ Log.info(
+ 'Opened message ${messageUpdate.multipleTargetMessageIds}',
+ );
+ try {
+ await twonlyDB.messagesDao.handleMessagesOpened(
+ contactId,
+ messageUpdate.multipleTargetMessageIds,
+ fromTimestamp(messageUpdate.timestamp),
);
- try {
- await twonlyDB.messagesDao.handleMessageOpened(
- contactId,
- targetMessageId,
- fromTimestamp(messageUpdate.timestamp),
- );
- } catch (e) {
- Log.warn(e);
- }
+ } catch (e) {
+ Log.warn(e);
}
case EncryptedContent_MessageUpdate_Type.DELETE:
if (!await isSender(contactId, messageUpdate.senderMessageId)) {
diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart
index b4452eb..2e83f50 100644
--- a/lib/src/services/api/messages.dart
+++ b/lib/src/services/api/messages.dart
@@ -375,15 +375,18 @@ Future notifyContactAboutOpeningMessage(
),
blocking: false,
);
- for (final messageId in messageOtherIds) {
- await twonlyDB.messagesDao.updateMessageId(
- messageId,
- MessagesCompanion(
- openedAt: Value(actionAt),
- openedByAll: Value(actionAt),
- ),
- );
- }
+ await twonlyDB.batch((batch) {
+ for (final messageId in messageOtherIds) {
+ batch.update(
+ twonlyDB.messages,
+ MessagesCompanion(
+ openedAt: Value(actionAt),
+ openedByAll: Value(actionAt),
+ ),
+ where: (tbl) => tbl.messageId.equals(messageId),
+ );
+ }
+ });
await updateLastMessageId(contactId, biggestMessageId);
}
diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart
index 5e84cfe..3f88459 100644
--- a/lib/src/services/api/server_messages.dart
+++ b/lib/src/services/api/server_messages.dart
@@ -32,38 +32,41 @@ import 'package:twonly/src/utils/misc.dart';
final lockHandleServerMessage = Mutex();
Future handleServerMessage(server.ServerToClient msg) async {
- /// Returns means, that the server can delete the message from the server.
- final ok = client.Response_Ok()..none = true;
- var response = client.Response()..ok = ok;
+ return lockHandleServerMessage.protect(() async {
+ /// Returns means, that the server can delete the message from the server.
+ final ok = client.Response_Ok()..none = true;
+ var response = client.Response()..ok = ok;
- try {
- if (msg.v0.hasRequestNewPreKeys()) {
- response = await handleRequestNewPreKey();
- } else if (msg.v0.hasNewMessage()) {
- await handleClient2ClientMessage(msg.v0.newMessage);
- } else if (msg.v0.hasNewMessages()) {
- for (final newMessage in msg.v0.newMessages.newMessages) {
- try {
- await handleClient2ClientMessage(newMessage);
- } catch (e) {
- Log.error(e);
+ try {
+ if (msg.v0.hasRequestNewPreKeys()) {
+ response = await handleRequestNewPreKey();
+ } else if (msg.v0.hasNewMessage()) {
+ await handleClient2ClientMessage(msg.v0.newMessage);
+ } else if (msg.v0.hasNewMessages()) {
+ for (final newMessage in msg.v0.newMessages.newMessages) {
+ try {
+ await handleClient2ClientMessage(newMessage);
+ } catch (e) {
+ Log.error(e);
+ }
}
+ } else {
+ Log.error('Unknown server message: $msg');
}
- } else {
- Log.error('Unknown server message: $msg');
+ } catch (e) {
+ Log.error(e);
}
- } catch (e) {
- Log.error(e);
- }
- final v0 = client.V0()
- ..seq = msg.v0.seq
- ..response = response;
+ final v0 = client.V0()
+ ..seq = msg.v0.seq
+ ..response = response;
- await apiService.sendResponse(ClientToServer()..v0 = v0);
+ await apiService.sendResponse(ClientToServer()..v0 = v0);
+ });
}
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
+bool alreadyPerformedAnResync = false;
Mutex protectReceiptCheck = Mutex();
diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.dart
index facc198..38d5501 100644
--- a/lib/src/services/api/utils.dart
+++ b/lib/src/services/api/utils.dart
@@ -84,7 +84,7 @@ Future handleMediaError(MediaFile media) async {
Future importSignalContactAndCreateRequest(
server.Response_UserData userdata,
) async {
- if (await createNewSignalSession(userdata)) {
+ if (await processSignalUserData(userdata)) {
// 1. Setup notifications keys with the other user
await setupNotificationWithUsers(
forceContact: userdata.userId.toInt(),
diff --git a/lib/src/services/group.services.dart b/lib/src/services/group.services.dart
index e9c8697..e975778 100644
--- a/lib/src/services/group.services.dart
+++ b/lib/src/services/group.services.dart
@@ -472,7 +472,7 @@ Future addNewHiddenContact(int contactId) async {
const Value(true), // this will hide the contact in the contact list
),
);
- await createNewSignalSession(userData);
+ await processSignalUserData(userData);
unawaited(setupNotificationWithUsers(forceContact: contactId));
return true;
}
diff --git a/lib/src/services/mediafiles/compression.service.dart b/lib/src/services/mediafiles/compression.service.dart
index d2e492b..a5a828c 100644
--- a/lib/src/services/mediafiles/compression.service.dart
+++ b/lib/src/services/mediafiles/compression.service.dart
@@ -1,14 +1,14 @@
import 'dart:async';
import 'dart:io';
import 'package:drift/drift.dart' show Value;
-import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
-import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
+import 'package:pro_video_editor/pro_video_editor.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/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart';
+import 'package:video_compress/video_compress.dart';
Future compressImage(
File sourceFile,
@@ -69,51 +69,47 @@ Future compressAndOverlayVideo(MediaFileService media) async {
media.ffmpegOutputPath.deleteSync();
}
- if (gUser.disableVideoCompression) {
- media.originalPath.copySync(media.tempPath.path);
- return;
- }
-
- var overLayCommand = '';
- if (media.overlayImagePath.existsSync()) {
- if (Platform.isAndroid) {
- overLayCommand =
- '-i "${media.overlayImagePath.path}" -filter_complex "[1:v]format=yuva420p[ovr_in];[0:v]format=yuv420p[base_in];[ovr_in][base_in]scale2ref=w=rw:h=rh[ovr_out][base_out];[base_out][ovr_out]overlay=0:0"';
- } else {
- overLayCommand =
- '-i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0"';
- }
- }
-
final stopwatch = Stopwatch()..start();
- var additionalParams = '';
+ try {
+ final task = VideoRenderData(
+ video: EditorVideo.file(media.originalPath),
+ // qualityPreset: VideoQualityPreset.p720High,
+ imageBytes: media.overlayImagePath.readAsBytesSync(),
+ enableAudio: !media.removeAudio,
+ );
- if (Platform.isAndroid) {
- additionalParams += ' -c:v libx264';
- }
+ final result = await ProVideoEditor.instance.renderVideo(task);
+ media.ffmpegOutputPath.writeAsBytesSync(result);
- var command =
- '-i "${media.originalPath.path}" $overLayCommand -map "0:a?" $additionalParams -preset veryfast -crf 28 -c:a aac -b:a 64k "${media.ffmpegOutputPath.path}"';
+ MediaInfo? mediaInfo;
+ try {
+ mediaInfo = await VideoCompress.compressVideo(
+ media.ffmpegOutputPath.path,
+ quality: VideoQuality.Res640x480Quality,
+ includeAudio: true,
+ );
+ Log.info('Video has now size of ${mediaInfo!.filesize} bytes.');
+ } catch (e) {
+ Log.error('during video compression: $e');
+ }
- if (media.removeAudio) {
- command =
- '-i "${media.originalPath.path}" $overLayCommand $additionalParams -preset veryfast -crf 28 -an "${media.ffmpegOutputPath.path}"';
- }
+ if (mediaInfo == null) {
+ Log.error('Could not compress video using original video.');
+ // as a fall back use the non compressed version
+ media.ffmpegOutputPath.copySync(media.tempPath.path);
+ } else {
+ mediaInfo.file!.copySync(media.tempPath.path);
+ }
- final session = await FFmpegKit.execute(command);
- final returnCode = await session.getReturnCode();
-
- if (ReturnCode.isSuccess(returnCode)) {
- media.ffmpegOutputPath.copySync(media.tempPath.path);
stopwatch.stop();
Log.info(
- 'It took ${stopwatch.elapsedMilliseconds}ms to compress the video',
+ 'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from ${media.ffmpegOutputPath.statSync().size} to ${media.tempPath.statSync().size} bytes.',
);
- } else {
- Log.info(command);
- Log.error('Compression failed for the video with exit code $returnCode.');
- Log.error(await session.getAllLogsAsString());
+ } catch (e) {
+ Log.error(e);
+ // Log.error('Compression failed for the video with exit code $returnCode.');
+ // Log.error(await session.getAllLogsAsString());
// This should not happen, but in case "notify" the user that the video was not send... This is absolutely bad, but
// better this way then sending an uncompressed media file which potentially is 100MB big :/
// Hopefully the user will report the strange behavior <3
diff --git a/lib/src/services/mediafiles/thumbnail.service.dart b/lib/src/services/mediafiles/thumbnail.service.dart
index 53275b3..c6c0d81 100644
--- a/lib/src/services/mediafiles/thumbnail.service.dart
+++ b/lib/src/services/mediafiles/thumbnail.service.dart
@@ -1,6 +1,6 @@
import 'dart:io';
-import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
-import 'package:ffmpeg_kit_flutter_new/return_code.dart';
+import 'dart:ui';
+import 'package:pro_video_editor/pro_video_editor.dart';
import 'package:twonly/src/utils/log.dart';
Future createThumbnailsForVideo(
@@ -13,22 +13,26 @@ Future createThumbnailsForVideo(
return;
}
- final command =
- '-y -i "${sourceFile.path}" -ss 00:00:00 -vframes 1 -vf "scale=iw:ih:flags=lanczos" -c:v libwebp -q:v 100 -compression_level 6 "${destinationFile.path}"';
+ final images = await ProVideoEditor.instance.getThumbnails(
+ ThumbnailConfigs(
+ video: EditorVideo.file(sourceFile),
+ outputFormat: ThumbnailFormat.webp,
+ timestamps: const [
+ Duration.zero,
+ ],
+ outputSize: const Size(272, 153),
+ ),
+ );
- final session = await FFmpegKit.execute(command);
- final returnCode = await session.getReturnCode();
-
- if (ReturnCode.isSuccess(returnCode)) {
+ if (images.isNotEmpty) {
stopwatch.stop();
+ destinationFile.writeAsBytesSync(images.first);
Log.info(
'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.',
);
} else {
- Log.info(command);
Log.error(
- 'Thumbnail creation failed for the video with exit code $returnCode.',
+ 'Thumbnail creation failed for the video with exit code.',
);
- Log.error(await session.getAllLogsAsString());
}
}
diff --git a/lib/src/services/signal/encryption.signal.dart b/lib/src/services/signal/encryption.signal.dart
index d499f95..a565f32 100644
--- a/lib/src/services/signal/encryption.signal.dart
+++ b/lib/src/services/signal/encryption.signal.dart
@@ -1,9 +1,12 @@
import 'dart:typed_data';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
+// ignore: implementation_imports
+import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart';
import 'package:mutex/mutex.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
-import 'package:twonly/src/services/signal/consts.signal.dart';
+import 'package:twonly/src/services/api/messages.dart';
+import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart';
@@ -17,7 +20,7 @@ Future signalEncryptMessage(
return lockingSignalEncryption.protect(() async {
try {
final signalStore = (await getSignalStore())!;
- final address = SignalProtocolAddress(target.toString(), defaultDeviceId);
+ final address = getSignalAddress(target);
final session = SessionCipher.fromStore(signalStore, address);
return await session.encrypt(plaintextContent);
} catch (e) {
@@ -27,16 +30,18 @@ Future signalEncryptMessage(
});
}
+bool alreadyPerformedAnResync = false;
+
Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
signalDecryptMessage(
- int source,
+ int fromUserId,
Uint8List encryptedContentRaw,
int type,
) async {
try {
final session = SessionCipher.fromStore(
(await getSignalStore())!,
- SignalProtocolAddress(source.toString(), defaultDeviceId),
+ getSignalAddress(fromUserId),
);
Uint8List plaintext;
@@ -59,6 +64,26 @@ Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
} on InvalidKeyIdException catch (e) {
Log.warn(e);
return (null, PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN);
+ } on InvalidMessageException catch (e) {
+ if (!alreadyPerformedAnResync) {
+ if (await handleSessionResync(fromUserId)) {
+ // This flag prevents from resyncing the session the client received multiple new
+ // messages from the server he could not decrypt
+ alreadyPerformedAnResync = true;
+
+ // This message contains a new PreKeyBundle establishing a new signal session
+ await sendCipherText(
+ fromUserId,
+ EncryptedContent(
+ errorMessages: EncryptedContent_ErrorMessages(
+ type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
+ ),
+ ),
+ );
+ }
+ }
+ Log.warn(e);
+ return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
} catch (e) {
Log.error(e);
return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
diff --git a/lib/src/services/signal/session.signal.dart b/lib/src/services/signal/session.signal.dart
index 6e48445..e00520e 100644
--- a/lib/src/services/signal/session.signal.dart
+++ b/lib/src/services/signal/session.signal.dart
@@ -6,17 +6,14 @@ import 'package:twonly/src/services/signal/consts.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart';
-Future createNewSignalSession(Response_UserData userData) async {
+Future processSignalUserData(Response_UserData userData) async {
final SignalProtocolStore? signalStore = await getSignalStore();
if (signalStore == null) {
return false;
}
- final targetAddress = SignalProtocolAddress(
- userData.userId.toString(),
- defaultDeviceId,
- );
+ final targetAddress = getSignalAddress(userData.userId.toInt());
final sessionBuilder = SessionBuilder.fromSignalStore(
signalStore,
@@ -82,30 +79,6 @@ Future deleteSessionWithTarget(int target) async {
await signalStore.sessionStore.deleteSession(address);
}
-Future generateSessionFingerPrint(int target) async {
- final signalStore = await getSignalStore();
- if (signalStore == null) return null;
- try {
- final targetIdentity = await signalStore
- .getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
- if (targetIdentity != null) {
- final generator = NumericFingerprintGenerator(5200);
- final localFingerprint = generator.createFor(
- 1,
- Uint8List.fromList([gUser.userId]),
- (await signalStore.getIdentityKeyPair()).getPublicKey(),
- Uint8List.fromList([target]),
- targetIdentity,
- );
-
- return localFingerprint;
- }
- return null;
- } catch (e) {
- return null;
- }
-}
-
Future getPublicKeyFromContact(int contactId) async {
final signalStore = await getSignalStore();
if (signalStore == null) return null;
@@ -124,3 +97,13 @@ Future getPublicKeyFromContact(int contactId) async {
return null;
}
}
+
+Future handleSessionResync(int fromUserId) async {
+ final userData = await apiService.getUserById(fromUserId);
+ if (userData != null) {
+ Log.info('Got new session data from the server to re-sync the session');
+ return processSignalUserData(userData);
+ }
+ Log.info('Could not download userdata from the server.');
+ return false;
+}
diff --git a/lib/src/services/signal/utils.signal.dart b/lib/src/services/signal/utils.signal.dart
index ef4a1cd..dc8ef36 100644
--- a/lib/src/services/signal/utils.signal.dart
+++ b/lib/src/services/signal/utils.signal.dart
@@ -1,6 +1,7 @@
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/model/json/signal_identity.dart';
+import 'package:twonly/src/services/signal/consts.signal.dart';
import 'package:twonly/src/services/signal/identity.signal.dart';
Future getSignalStore() async {
@@ -18,3 +19,7 @@ Future getSignalStoreFromIdentity(
signalIdentity.registrationId,
);
}
+
+SignalProtocolAddress getSignalAddress(int userId) {
+ return SignalProtocolAddress(userId.toString(), defaultDeviceId);
+}
diff --git a/lib/src/views/camera/share_image_editor.view.dart b/lib/src/views/camera/share_image_editor.view.dart
index 9d12615..4e31b88 100644
--- a/lib/src/views/camera/share_image_editor.view.dart
+++ b/lib/src/views/camera/share_image_editor.view.dart
@@ -444,7 +444,7 @@ class _ShareImageEditorView extends State {
setState(() {});
// Make a short delay, so the setState does have its effect...
- await Future.delayed(const Duration(milliseconds: 10));
+ await Future.delayed(const Duration(milliseconds: 80));
final image = await screenshotController.capture(
pixelRatio: pixelRatio,
diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart
index fab9358..df66b41 100644
--- a/lib/src/views/chats/chat_messages.view.dart
+++ b/lib/src/views/chats/chat_messages.view.dart
@@ -33,7 +33,6 @@ class ChatItem {
const ChatItem._({
this.message,
this.date,
- this.lastOpenedPosition,
this.groupAction,
});
factory ChatItem.date(DateTime date) {
@@ -42,20 +41,15 @@ class ChatItem {
factory ChatItem.message(Message message) {
return ChatItem._(message: message);
}
- factory ChatItem.lastOpenedPosition(List contacts) {
- return ChatItem._(lastOpenedPosition: contacts);
- }
factory ChatItem.groupAction(GroupHistory groupAction) {
return ChatItem._(groupAction: groupAction);
}
final GroupHistory? groupAction;
final Message? message;
final DateTime? date;
- final List? lastOpenedPosition;
bool get isMessage => message != null;
bool get isDate => date != null;
bool get isGroupAction => groupAction != null;
- bool get isLastOpenedPosition => lastOpenedPosition != null;
}
/// Displays detailed information about a SampleItem.
@@ -75,14 +69,11 @@ class _ChatMessagesViewState extends State {
late StreamSubscription> messageSub;
StreamSubscription>? groupActionsSub;
StreamSubscription>? contactSub;
- StreamSubscription>>?
- lastOpenedMessageByContactSub;
Map userIdToContact = {};
List messages = [];
List allMessages = [];
- List<(Message, Contact)> lastOpenedMessageByContact = [];
List groupActions = [];
List galleryItems = [];
Message? quotesMessage;
@@ -105,7 +96,6 @@ class _ChatMessagesViewState extends State {
messageSub.cancel();
contactSub?.cancel();
groupActionsSub?.cancel();
- lastOpenedMessageByContactSub?.cancel();
super.dispose();
}
@@ -121,19 +111,10 @@ class _ChatMessagesViewState extends State {
});
if (!widget.group.isDirectChat) {
- final lastOpenedStream =
- twonlyDB.messagesDao.watchLastOpenedMessagePerContact(group.groupId);
- lastOpenedMessageByContactSub =
- lastOpenedStream.listen((lastActionsFuture) async {
- final update = await lastActionsFuture;
- lastOpenedMessageByContact = update;
- await setMessages(allMessages, update, groupActions);
- });
-
final actionsStream = twonlyDB.groupsDao.watchGroupActions(group.groupId);
groupActionsSub = actionsStream.listen((update) async {
groupActions = update;
- await setMessages(allMessages, lastOpenedMessageByContact, update);
+ await setMessages(allMessages, update);
});
final contactsStream = twonlyDB.contactsDao.watchAllContacts();
@@ -147,21 +128,14 @@ class _ChatMessagesViewState extends State {
final msgStream = twonlyDB.messagesDao.watchByGroupId(group.groupId);
messageSub = msgStream.listen((update) async {
allMessages = update;
-
- /// In case a message is not open yet the message is updated, which will trigger this watch to be called again.
- /// So as long as the Mutex is locked just return...
- if (protectMessageUpdating.isLocked) {
- // return;
- }
await protectMessageUpdating.protect(() async {
- await setMessages(update, lastOpenedMessageByContact, groupActions);
+ await setMessages(update, groupActions);
});
});
}
Future setMessages(
List newMessages,
- List<(Message, Contact)> lastOpenedMessageByContact,
List groupActions,
) async {
await flutterLocalNotificationsPlugin.cancelAll();
@@ -172,19 +146,7 @@ class _ChatMessagesViewState extends State {
DateTime? lastDate;
final openedMessages = >{};
- final lastOpenedMessageToContact = >{};
- final myLastMessageIndex =
- newMessages.lastIndexWhere((t) => t.senderId == null);
-
- for (final opened in lastOpenedMessageByContact) {
- if (!lastOpenedMessageToContact.containsKey(opened.$1.messageId)) {
- lastOpenedMessageToContact[opened.$1.messageId] = [opened.$2];
- } else {
- lastOpenedMessageToContact[opened.$1.messageId]!.add(opened.$2);
- }
- }
- var index = 0;
var groupHistoryIndex = 0;
for (final msg in newMessages) {
@@ -199,7 +161,6 @@ class _ChatMessagesViewState extends State {
}
}
}
- index += 1;
if (msg.type != MessageType.media.name &&
msg.senderId != null &&
msg.openedAt == null) {
@@ -221,16 +182,6 @@ class _ChatMessagesViewState extends State {
lastDate = msg.createdAt;
}
chatItems.add(ChatItem.message(msg));
-
- if (index <= myLastMessageIndex || index == newMessages.length) {
- if (lastOpenedMessageToContact.containsKey(msg.messageId)) {
- chatItems.add(
- ChatItem.lastOpenedPosition(
- lastOpenedMessageToContact[msg.messageId]!,
- ),
- );
- }
- }
}
if (groupHistoryIndex < groupActions.length) {
for (var i = groupHistoryIndex; i < groupActions.length; i++) {
@@ -341,17 +292,6 @@ class _ChatMessagesViewState extends State {
return ChatDateChip(
item: messages[i],
);
- } else if (messages[i].isLastOpenedPosition) {
- return Wrap(
- spacing: 8,
- alignment: WrapAlignment.center,
- children: messages[i].lastOpenedPosition!.map((w) {
- return AvatarIcon(
- contactId: w.userId,
- fontSize: 12,
- );
- }).toList(),
- );
} else if (messages[i].isGroupAction) {
return ChatGroupAction(
key: Key(messages[i].groupAction!.groupHistoryId),
diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart
index 4a497b3..6668e78 100644
--- a/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart
+++ b/lib/src/views/chats/chat_messages_components/entries/chat_media_entry.dart
@@ -6,6 +6,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/memory_item.model.dart';
+import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
hide Message;
import 'package:twonly/src/services/api/mediafiles/download.service.dart'
@@ -13,8 +14,10 @@ import 'package:twonly/src/services/api/mediafiles/download.service.dart'
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
+import 'package:twonly/src/views/chats/chat_messages_components/entries/common.dart';
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
import 'package:twonly/src/views/chats/media_viewer.view.dart';
+import 'package:twonly/src/views/components/better_text.dart';
class ChatMediaEntry extends StatefulWidget {
const ChatMediaEntry({
@@ -114,31 +117,81 @@ class _ChatMediaEntryState extends State {
context,
);
- return GestureDetector(
- key: reopenMediaFile,
- onDoubleTap: onDoubleTap,
- onTap: (widget.message.type == MessageType.media.name) ? onTap : null,
- child: SizedBox(
- width: (widget.minWidth > 150) ? widget.minWidth : 150,
- height: (widget.message.mediaStored &&
- widget.mediaService.imagePreviewAvailable)
- ? 271
- : null,
- child: Align(
- alignment: Alignment.centerRight,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(12),
- child: InChatMediaViewer(
- message: widget.message,
- group: widget.group,
- mediaService: widget.mediaService,
- color: color,
- galleryItems: widget.galleryItems,
- canBeReopened: _canBeReopened,
+ var imageBorderRadius = BorderRadius.circular(12);
+
+ Widget additionalMessageData = Container();
+
+ final addData = widget.message.additionalMessageData;
+ if (addData != null) {
+ final info =
+ getBubbleInfo(context, widget.message, null, null, null, 200);
+ final data = AdditionalMessageData.fromBuffer(addData);
+ if (data.hasLink() && widget.message.mediaStored) {
+ imageBorderRadius = const BorderRadius.only(
+ topLeft: Radius.circular(12),
+ topRight: Radius.circular(12),
+ bottomLeft: Radius.circular(5),
+ bottomRight: Radius.circular(5),
+ );
+
+ additionalMessageData = Container(
+ constraints: BoxConstraints(
+ maxWidth: MediaQuery.of(context).size.width * 0.8,
+ ),
+ padding:
+ const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10),
+ decoration: BoxDecoration(
+ color: info.color,
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(5),
+ topRight: Radius.circular(12),
+ bottomLeft: Radius.circular(12),
+ bottomRight: Radius.circular(12),
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ BetterText(text: data.link, textColor: info.textColor),
+ ],
+ ),
+ );
+ }
+ }
+
+ return Column(
+ crossAxisAlignment: widget.message.senderId == null
+ ? CrossAxisAlignment.end
+ : CrossAxisAlignment.start,
+ children: [
+ GestureDetector(
+ key: reopenMediaFile,
+ onDoubleTap: onDoubleTap,
+ onTap: (widget.message.type == MessageType.media.name) ? onTap : null,
+ child: SizedBox(
+ width: (widget.minWidth > 150) ? widget.minWidth : 150,
+ height: (widget.message.mediaStored &&
+ widget.mediaService.imagePreviewAvailable)
+ ? 271
+ : null,
+ child: Align(
+ alignment: Alignment.centerRight,
+ child: ClipRRect(
+ borderRadius: imageBorderRadius,
+ child: InChatMediaViewer(
+ message: widget.message,
+ group: widget.group,
+ mediaService: widget.mediaService,
+ color: color,
+ galleryItems: widget.galleryItems,
+ canBeReopened: _canBeReopened,
+ ),
+ ),
),
),
),
- ),
+ additionalMessageData,
+ ],
);
}
}
diff --git a/lib/src/views/components/better_list_title.dart b/lib/src/views/components/better_list_title.dart
index fac2f5e..e15aca3 100644
--- a/lib/src/views/components/better_list_title.dart
+++ b/lib/src/views/components/better_list_title.dart
@@ -3,7 +3,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class BetterListTile extends StatelessWidget {
const BetterListTile({
- required this.text,
+ this.text,
+ this.textWidget,
this.onTap,
this.icon,
this.leading,
@@ -17,7 +18,8 @@ class BetterListTile extends StatelessWidget {
final IconData? icon;
final Widget? leading;
final Widget? trailing;
- final String text;
+ final String? text;
+ final Widget? textWidget;
final Widget? subtitle;
final Color? color;
final VoidCallback? onTap;
@@ -40,10 +42,12 @@ class BetterListTile extends StatelessWidget {
),
),
trailing: trailing,
- title: Text(
- text,
- style: TextStyle(color: color),
- ),
+ title: text != null
+ ? Text(
+ text!,
+ style: TextStyle(color: color),
+ )
+ : textWidget,
subtitle: subtitle,
onTap: onTap,
);
diff --git a/lib/src/views/components/svg_icon.dart b/lib/src/views/components/svg_icon.dart
index efd067a..8691f59 100644
--- a/lib/src/views/components/svg_icon.dart
+++ b/lib/src/views/components/svg_icon.dart
@@ -3,7 +3,6 @@ import 'package:flutter_svg/flutter_svg.dart';
class SvgIcons {
static const String verifiedGreen = 'assets/icons/verified_badge_green.svg';
- static const String verifiedYellow = 'assets/icons/verified_badge_yellow.svg';
static const String verifiedRed = 'assets/icons/verified_badge_red.svg';
}
diff --git a/lib/src/views/components/verified_shield.dart b/lib/src/views/components/verified_shield.dart
index a18684d..f2042d7 100644
--- a/lib/src/views/components/verified_shield.dart
+++ b/lib/src/views/components/verified_shield.dart
@@ -37,7 +37,7 @@ class _VerifiedShieldState extends State {
contact = contacts.first;
}
setState(() {
- isVerified = contacts.any((t) => t.verified);
+ isVerified = contacts.every((t) => t.verified);
});
});
} else if (widget.contact != null) {
diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart
index fcfae5f..8f56bda 100644
--- a/lib/src/views/groups/group.view.dart
+++ b/lib/src/views/groups/group.view.dart
@@ -242,7 +242,18 @@ class _GroupViewState extends State {
contactId: member.$1.userId,
fontSize: 16,
),
- text: getContactDisplayName(member.$1, maxLength: 25),
+ textWidget: Row(
+ children: [
+ Text(getContactDisplayName(member.$1, maxLength: 25)),
+ Padding(
+ padding: const EdgeInsets.only(left: 5),
+ child: VerifiedShield(
+ key: Key(member.$2.contactId.toString()),
+ contact: member.$1,
+ ),
+ ),
+ ],
+ ),
trailing: (member.$2.memberState == MemberState.admin)
? Text(context.lang.admin)
: null,
diff --git a/lib/src/views/settings/developer/automated_testing.view.dart b/lib/src/views/settings/developer/automated_testing.view.dart
index d4beded..aafa579 100644
--- a/lib/src/views/settings/developer/automated_testing.view.dart
+++ b/lib/src/views/settings/developer/automated_testing.view.dart
@@ -2,8 +2,10 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/services/api/messages.dart';
+import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@@ -26,42 +28,92 @@ class _AutomatedTestingViewState extends State {
@override
Widget build(BuildContext context) {
+ if (kReleaseMode) return Container();
return Scaffold(
appBar: AppBar(
title: const Text('Automated Testing'),
),
body: ListView(
children: [
- if (!kReleaseMode)
- ListTile(
- title: const Text('Sending a lot of messages.'),
- subtitle: Text(lotsOfMessagesStatus),
- onTap: () async {
- final username = await showUserNameDialog(context);
- if (username == null) return;
- Log.info('Requested to send to $username');
+ ListTile(
+ title: const Text('Trigger Signal Out-Of-Sync'),
+ onTap: () async {
+ final username = await showUserNameDialog(context);
+ if (username == null) return;
+ final contacts = await twonlyDB.contactsDao
+ .getContactsByUsername(username.toLowerCase());
+ if (contacts.length != 1) {
+ Log.error('No single user fund');
+ return;
+ }
+ final userId = contacts.first.userId;
- final contacts = await twonlyDB.contactsDao
- .getContactsByUsername(username.toLowerCase());
+ final group = await twonlyDB.groupsDao.getDirectChat(userId);
+ if (group == null) {
+ Log.error('Target user must have a group!');
+ return;
+ }
- for (final contact in contacts) {
- Log.info('Sending to ${contact.username}');
- final group =
- await twonlyDB.groupsDao.getDirectChat(contact.userId);
- for (var i = 0; i < 200; i++) {
- setState(() {
- lotsOfMessagesStatus =
- 'At message $i to ${contact.username}.';
- });
- await insertAndSendTextMessage(
- group!.groupId,
- 'Message $i.',
- null,
- );
- }
+ final sessionStore = await getSignalStore();
+
+ // 1. Store a valid session
+ final originalSession =
+ await sessionStore!.loadSession(getSignalAddress(userId));
+ final serializedSession = originalSession.serialize();
+
+ for (var i = 0; i < 10; i++) {
+ await insertAndSendTextMessage(
+ group.groupId,
+ 'DesyncTest_1',
+ null,
+ );
+ }
+
+ final corruptedSession =
+ SessionRecord.fromSerialized(serializedSession);
+ await sessionStore.storeSession(
+ getSignalAddress(userId),
+ corruptedSession,
+ );
+
+ await insertAndSendTextMessage(
+ group.groupId,
+ 'DesyncTest_2',
+ null,
+ );
+
+ // The other client should res
+ },
+ ),
+ ListTile(
+ title: const Text('Sending a lot of messages.'),
+ subtitle: Text(lotsOfMessagesStatus),
+ onTap: () async {
+ final username = await showUserNameDialog(context);
+ if (username == null) return;
+ Log.info('Requested to send to $username');
+
+ final contacts = await twonlyDB.contactsDao
+ .getContactsByUsername(username.toLowerCase());
+
+ for (final contact in contacts) {
+ Log.info('Sending to ${contact.username}');
+ final group =
+ await twonlyDB.groupsDao.getDirectChat(contact.userId);
+ for (var i = 0; i < 200; i++) {
+ setState(() {
+ lotsOfMessagesStatus =
+ 'At message $i to ${contact.username}.';
+ });
+ await insertAndSendTextMessage(
+ group!.groupId,
+ 'Message $i.',
+ null,
+ );
}
- },
- ),
+ }
+ },
+ ),
],
),
);
diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart
index 37413cf..920eba9 100644
--- a/lib/src/views/settings/developer/developer.view.dart
+++ b/lib/src/views/settings/developer/developer.view.dart
@@ -29,14 +29,6 @@ class _DeveloperSettingsViewState extends State {
setState(() {});
}
- Future toggleVideoCompression() async {
- await updateUserdata((u) {
- u.disableVideoCompression = !u.disableVideoCompression;
- return u;
- });
- setState(() {});
- }
-
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -75,17 +67,6 @@ class _DeveloperSettingsViewState extends State {
}
},
),
- ListTile(
- title: const Text('Disable ffmpeg'),
- subtitle: const Text(
- 'If your smartphone crashes, you can disable ffmpeg. This will prevent your videos from being compressed and NO FILTER will be applied to the video! This is a workaround, until the root-cause in ffmpeg is found.',
- ),
- onTap: toggleVideoCompression,
- trailing: Switch(
- value: gUser.disableVideoCompression,
- onChanged: (a) => toggleVideoCompression(),
- ),
- ),
if (!kReleaseMode)
ListTile(
title: const Text('Automated Testing'),
diff --git a/lib/src/views/settings/help/diagnostics.view.dart b/lib/src/views/settings/help/diagnostics.view.dart
index a96cd1c..faa5bc0 100644
--- a/lib/src/views/settings/help/diagnostics.view.dart
+++ b/lib/src/views/settings/help/diagnostics.view.dart
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/utils/log.dart';
+import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/loader.dart';
class DiagnosticsView extends StatefulWidget {
@@ -150,14 +151,19 @@ class _LogViewerWidgetState extends State {
}
TextSpan _formatLineSpan(_LogEntry e) {
- final tsStyle =
- TextStyle(color: Colors.grey.shade500, fontFamily: 'monospace');
+ final tsStyle = TextStyle(
+ color: isDarkMode(context) ? Colors.white : Colors.black,
+ fontFamily: 'monospace',
+ );
final levelStyle = TextStyle(
color: Colors.blueGrey.shade600,
fontWeight: FontWeight.bold,
fontFamily: 'monospace',
);
- const msgStyle = TextStyle(fontFamily: 'monospace');
+ final msgStyle = TextStyle(
+ color: isDarkMode(context) ? Colors.white : Colors.black,
+ fontFamily: 'monospace',
+ );
return TextSpan(
children: [
diff --git a/lib/src/views/settings/help/faq/verifybadge.dart b/lib/src/views/settings/help/faq/verifybadge.dart
index f3efb3d..b0568a7 100644
--- a/lib/src/views/settings/help/faq/verifybadge.dart
+++ b/lib/src/views/settings/help/faq/verifybadge.dart
@@ -30,7 +30,11 @@ class _VerificationBadeFaqViewState extends State {
description: context.lang.verificationBadgeGreenDesc,
),
_buildItem(
- icon: const SvgIcon(assetPath: SvgIcons.verifiedYellow, size: 40),
+ icon: const SvgIcon(
+ assetPath: SvgIcons.verifiedGreen,
+ size: 40,
+ color: Color.fromARGB(255, 227, 227, 3),
+ ),
description: context.lang.verificationBadgeYellowDesc,
),
_buildItem(
diff --git a/pubspec.lock b/pubspec.lock
index 240bf52..3ee608d 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -456,22 +456,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
- ffmpeg_kit_flutter_new:
- dependency: "direct main"
- description:
- name: ffmpeg_kit_flutter_new
- sha256: d127635f27e93a7f21f0a14ce0a1a148e80919c402dac4a2118d73bfb17ce841
- url: "https://pub.dev"
- source: hosted
- version: "4.1.0"
- ffmpeg_kit_flutter_platform_interface:
- dependency: transitive
- description:
- name: ffmpeg_kit_flutter_platform_interface
- sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee
- url: "https://pub.dev"
- source: hosted
- version: "0.2.1"
file:
dependency: transitive
description:
@@ -1513,6 +1497,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.3"
+ pro_video_editor:
+ dependency: "direct main"
+ description:
+ name: pro_video_editor
+ sha256: "0d985f7653c59e2b521d19db49351476eb74eb4001689b33fb8112ab1a9c4330"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.6.1"
protobuf:
dependency: "direct main"
description:
@@ -1996,6 +1988,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "10.1.0"
+ video_compress:
+ dependency: "direct main"
+ description:
+ name: video_compress
+ sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.4"
video_player:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 097f6c6..9eb29ca 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
publish_to: 'none'
-version: 0.0.94+94
+version: 0.0.97+97
environment:
sdk: ^3.6.0
@@ -83,7 +83,6 @@ dependencies:
cached_network_image: ^3.4.1
cryptography_flutter_plus: ^2.3.4
cryptography_plus: ^2.7.0
- ffmpeg_kit_flutter_new: ^4.1.0
flutter_android_volume_keydown: ^1.0.1
flutter_image_compress: ^2.4.0
flutter_volume_controller: ^1.3.4
@@ -113,6 +112,8 @@ dependencies:
flutter_sharing_intent: ^2.0.4
no_screenshot: ^0.3.1
google_mlkit_face_detection: ^0.13.1
+ pro_video_editor: ^1.6.1
+ video_compress: ^3.1.4
dependency_overrides:
dots_indicator: