diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index f1d72ff9..e805edfb 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -368,10 +368,12 @@ class EncryptedContent_GroupCreate extends $pb.GeneratedMessage { factory EncryptedContent_GroupCreate({ $core.List<$core.int>? stateKey, $core.List<$core.int>? groupPublicKey, + $core.String? groupName, }) { final result = create(); if (stateKey != null) result.stateKey = stateKey; if (groupPublicKey != null) result.groupPublicKey = groupPublicKey; + if (groupName != null) result.groupName = groupName; return result; } @@ -391,6 +393,7 @@ class EncryptedContent_GroupCreate extends $pb.GeneratedMessage { 3, _omitFieldNames ? '' : 'stateKey', $pb.PbFieldType.OY) ..a<$core.List<$core.int>>( 4, _omitFieldNames ? '' : 'groupPublicKey', $pb.PbFieldType.OY) + ..aOS(5, _omitFieldNames ? '' : 'groupName') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -433,6 +436,15 @@ class EncryptedContent_GroupCreate extends $pb.GeneratedMessage { $core.bool hasGroupPublicKey() => $_has(1); @$pb.TagNumber(4) void clearGroupPublicKey() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get groupName => $_getSZ(2); + @$pb.TagNumber(5) + set groupName($core.String value) => $_setString(2, value); + @$pb.TagNumber(5) + $core.bool hasGroupName() => $_has(2); + @$pb.TagNumber(5) + void clearGroupName() => $_clearField(5); } class EncryptedContent_GroupJoin extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/client/generated/messages.pbenum.dart b/lib/src/model/protobuf/client/generated/messages.pbenum.dart index 31f04f9b..699b8ee9 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbenum.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbenum.dart @@ -79,16 +79,20 @@ class EncryptedContent_ErrorMessages_Type extends $pb.ProtobufEnum { static const EncryptedContent_ErrorMessages_Type SESSION_OUT_OF_SYNC = EncryptedContent_ErrorMessages_Type._( 3, _omitEnumNames ? '' : 'SESSION_OUT_OF_SYNC'); + static const EncryptedContent_ErrorMessages_Type + GROUP_NOT_FOUND_OR_NOT_A_MEMBER = EncryptedContent_ErrorMessages_Type._( + 4, _omitEnumNames ? '' : 'GROUP_NOT_FOUND_OR_NOT_A_MEMBER'); static const $core.List values = [ ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD, UNKNOWN_MESSAGE_TYPE, SESSION_OUT_OF_SYNC, + GROUP_NOT_FOUND_OR_NOT_A_MEMBER, ]; static final $core.List _byValue = - $pb.ProtobufEnum.$_initByValueList(values, 3); + $pb.ProtobufEnum.$_initByValueList(values, 4); static EncryptedContent_ErrorMessages_Type? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value]; diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index c0a5fb94..96f73817 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -465,6 +465,7 @@ const EncryptedContent_ErrorMessages_Type$json = { {'1': 'ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD', '2': 0}, {'1': 'UNKNOWN_MESSAGE_TYPE', '2': 2}, {'1': 'SESSION_OUT_OF_SYNC', '2': 3}, + {'1': 'GROUP_NOT_FOUND_OR_NOT_A_MEMBER', '2': 4}, ], }; @@ -474,6 +475,18 @@ const EncryptedContent_GroupCreate$json = { '2': [ {'1': 'state_key', '3': 3, '4': 1, '5': 12, '10': 'stateKey'}, {'1': 'group_public_key', '3': 4, '4': 1, '5': 12, '10': 'groupPublicKey'}, + { + '1': 'group_name', + '3': 5, + '4': 1, + '5': 9, + '9': 0, + '10': 'groupName', + '17': true + }, + ], + '8': [ + {'1': '_group_name'}, ], }; @@ -975,78 +988,79 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( 'aXNjb3ZlcnlSZXF1ZXN0iAEBEl4KFXVzZXJfZGlzY292ZXJ5X3VwZGF0ZRgXIAEoCzIlLkVuY3' 'J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVVwZGF0ZUgWUhN1c2VyRGlzY292ZXJ5VXBkYXRl' 'iAEBEmEKFmtleV92ZXJpZmljYXRpb25fcHJvb2YYGCABKAsyJi5FbmNyeXB0ZWRDb250ZW50Lk' - 'tleVZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBGvABCg1FcnJv' + 'tleVZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBGpYCCg1FcnJv' 'ck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZX' 'MuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRfaWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0' - 'SWQidwoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1' - 'JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVTU0FHRV9UWVBFEAISFwoTU0VTU0lPTl9P' - 'VVRfT0ZfU1lOQxADGlQKC0dyb3VwQ3JlYXRlEhsKCXN0YXRlX2tleRgDIAEoDFIIc3RhdGVLZX' - 'kSKAoQZ3JvdXBfcHVibGljX2tleRgEIAEoDFIOZ3JvdXBQdWJsaWNLZXkaNQoJR3JvdXBKb2lu' - 'EigKEGdyb3VwX3B1YmxpY19rZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3' - 'VwUHVibGljS2V5GsgCCgtHcm91cFVwZGF0ZRIqChFncm91cF9hY3Rpb25fdHlwZRgBIAEoCVIP' - 'Z3JvdXBBY3Rpb25UeXBlEjMKE2FmZmVjdGVkX2NvbnRhY3RfaWQYAiABKANIAFIRYWZmZWN0ZW' - 'RDb250YWN0SWSIAQESKQoObmV3X2dyb3VwX25hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEB' - 'ElcKJm5ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGAQgASgDSAJSIm5ld0' - 'RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHOIAQFCFgoUX2FmZmVjdGVkX2NvbnRhY3Rf' - 'aWRCEQoPX25ld19ncm91cF9uYW1lQikKJ19uZXdfZGVsZXRlX21lc3NhZ2VzX2FmdGVyX21pbG' - 'xpc2Vjb25kcxqvAQoLVGV4dE1lc3NhZ2USKgoRc2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3Nl' - 'bmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZXh0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdG' - 'ltZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2VfaWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFC' - 'EwoRX3F1b3RlX21lc3NhZ2VfaWQazgEKFUFkZGl0aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZX' - 'JfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJ' - 'dGltZXN0YW1wEhIKBHR5cGUYAyABKAlSBHR5cGUSOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdG' - 'EYBCABKAxIAFIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3Nh' - 'Z2VfZGF0YRpkCghSZWFjdGlvbhIqChF0YXJnZXRfbWVzc2FnZV9pZBgBIAEoCVIPdGFyZ2V0TW' - 'Vzc2FnZUlkEhQKBWVtb2ppGAIgASgJUgVlbW9qaRIWCgZyZW1vdmUYAyABKAhSBnJlbW92ZRq+' - 'AgoNTWVzc2FnZVVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5NZXNzYW' - 'dlVXBkYXRlLlR5cGVSBHR5cGUSLwoRc2VuZGVyX21lc3NhZ2VfaWQYAiABKAlIAFIPc2VuZGVy' - 'TWVzc2FnZUlkiAEBEj0KG211bHRpcGxlX3RhcmdldF9tZXNzYWdlX2lkcxgDIAMoCVIYbXVsdG' - 'lwbGVUYXJnZXRNZXNzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3Rh' - 'bXAYBSABKANSCXRpbWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEg' - 'oKBk9QRU5FRBACQhQKEl9zZW5kZXJfbWVzc2FnZV9pZEIHCgVfdGV4dBqFBgoFTWVkaWESKgoR' - 'c2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGAIgASgOMh' - 'wuRW5jcnlwdGVkQ29udGVudC5NZWRpYS5UeXBlUgR0eXBlEkYKHWRpc3BsYXlfbGltaXRfaW5f' - 'bWlsbGlzZWNvbmRzGAMgASgDSABSGmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEjcKF3' - 'JlcXVpcmVzX2F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uEhwK' - 'CXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2VfaWQYBiABKAlIAV' - 'IOcXVvdGVNZXNzYWdlSWSIAQESKgoOZG93bmxvYWRfdG9rZW4YByABKAxIAlINZG93bmxvYWRU' - 'b2tlbogBARIqCg5lbmNyeXB0aW9uX2tleRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEioKDm' - 'VuY3J5cHRpb25fbWFjGAkgASgMSARSDWVuY3J5cHRpb25NYWOIAQESLgoQZW5jcnlwdGlvbl9u' - 'b25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNzYWdlX2' - 'RhdGEYCyABKAxIBlIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUkVVUExP' - 'QUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIgCh5fZGlzcG' - 'xheV9saW1pdF9pbl9taWxsaXNlY29uZHNCEwoRX3F1b3RlX21lc3NhZ2VfaWRCEQoPX2Rvd25s' - 'b2FkX3Rva2VuQhEKD19lbmNyeXB0aW9uX2tleUIRCg9fZW5jcnlwdGlvbl9tYWNCEwoRX2VuY3' - 'J5cHRpb25fbm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGqkBCgtNZWRpYVVwZGF0' - 'ZRI2CgR0eXBlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eX' - 'BlEioKEXRhcmdldF9tZXNzYWdlX2lkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIM' - 'CghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YW' - 'N0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVz' - 'dC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVB' - 'ACGqQCCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNv' - 'bnRhY3RVcGRhdGUuVHlwZVIEdHlwZRI3ChVhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAF' - 'ITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgB' - 'ARImCgxkaXNwbGF5X25hbWUYBCABKAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRV' - 'FVRVNUEAASCgoGVVBEQVRFEAFCGAoWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5h' - 'bWVCDwoNX2Rpc3BsYXlfbmFtZRrZAQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cH' - 'RlZENvbnRlbnQuUHVzaEtleXMuVHlwZVIEdHlwZRIaCgZrZXlfaWQYAiABKANIAFIFa2V5SWSI' - 'AQESFQoDa2V5GAMgASgMSAFSA2tleYgBARIiCgpjcmVhdGVkX2F0GAQgASgDSAJSCWNyZWF0ZW' - 'RBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIJCgdfa2V5X2lkQgYKBF9r' - 'ZXlCDQoLX2NyZWF0ZWRfYXQarwEKCUZsYW1lU3luYxIjCg1mbGFtZV9jb3VudGVyGAEgASgDUg' - 'xmbGFtZUNvdW50ZXISOQoZbGFzdF9mbGFtZV9jb3VudGVyX2NoYW5nZRgCIAEoA1IWbGFzdEZs' - 'YW1lQ291bnRlckNoYW5nZRIfCgtiZXN0X2ZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZBIhCgxmb3' - 'JjZV91cGRhdGUYBCABKAhSC2ZvcmNlVXBkYXRlGk0KD1R5cGluZ0luZGljYXRvchIbCglpc190' - 'eXBpbmcYASABKAhSCGlzVHlwaW5nEh0KCmNyZWF0ZWRfYXQYAiABKANSCWNyZWF0ZWRBdBo/Ch' - 'RVc2VyRGlzY292ZXJ5UmVxdWVzdBInCg9jdXJyZW50X3ZlcnNpb24YASABKAxSDmN1cnJlbnRW' - 'ZXJzaW9uGjEKE1VzZXJEaXNjb3ZlcnlVcGRhdGUSGgoIbWVzc2FnZXMYASADKAxSCG1lc3NhZ2' - 'VzGj0KFEtleVZlcmlmaWNhdGlvblByb29mEiUKDmNhbGN1bGF0ZWRfbWFjGAEgASgMUg1jYWxj' - 'dWxhdGVkTWFjQgsKCV9ncm91cF9pZEIRCg9faXNfZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm' - '9maWxlX2NvdW50ZXJCIAoeX3NlbmRlcl91c2VyX2Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tf' - 'Zm9yX2ZyaWVuZF9wcm9tb3Rpb25zQhEKD19tZXNzYWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX2' - '1lZGlhX3VwZGF0ZUIRCg9fY29udGFjdF91cGRhdGVCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtf' - 'ZmxhbWVfc3luY0IMCgpfcHVzaF9rZXlzQgsKCV9yZWFjdGlvbkIPCg1fdGV4dF9tZXNzYWdlQg' - '8KDV9ncm91cF9jcmVhdGVCDQoLX2dyb3VwX2pvaW5CDwoNX2dyb3VwX3VwZGF0ZUIaChhfcmVz' - 'ZW5kX2dyb3VwX3B1YmxpY19rZXlCEQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2' - 'RhdGFfbWVzc2FnZUITChFfdHlwaW5nX2luZGljYXRvckIZChdfdXNlcl9kaXNjb3ZlcnlfcmVx' - 'dWVzdEIYChZfdXNlcl9kaXNjb3ZlcnlfdXBkYXRlQhkKF19rZXlfdmVyaWZpY2F0aW9uX3Byb2' - '9m'); + 'SWQinAEKBFR5cGUSPAo4RVJST1JfUFJPQ0VTU0lOR19NRVNTQUdFX0NSRUFURURfQUNDT1VOVF' + '9SRVFVRVNUX0lOU1RFQUQQABIYChRVTktOT1dOX01FU1NBR0VfVFlQRRACEhcKE1NFU1NJT05f' + 'T1VUX09GX1NZTkMQAxIjCh9HUk9VUF9OT1RfRk9VTkRfT1JfTk9UX0FfTUVNQkVSEAQahwEKC0' + 'dyb3VwQ3JlYXRlEhsKCXN0YXRlX2tleRgDIAEoDFIIc3RhdGVLZXkSKAoQZ3JvdXBfcHVibGlj' + 'X2tleRgEIAEoDFIOZ3JvdXBQdWJsaWNLZXkSIgoKZ3JvdXBfbmFtZRgFIAEoCUgAUglncm91cE' + '5hbWWIAQFCDQoLX2dyb3VwX25hbWUaNQoJR3JvdXBKb2luEigKEGdyb3VwX3B1YmxpY19rZXkY' + 'ASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibGljS2V5GsgCCgtHcm91cF' + 'VwZGF0ZRIqChFncm91cF9hY3Rpb25fdHlwZRgBIAEoCVIPZ3JvdXBBY3Rpb25UeXBlEjMKE2Fm' + 'ZmVjdGVkX2NvbnRhY3RfaWQYAiABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESKQoObmV3X2' + 'dyb3VwX25hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElcKJm5ld19kZWxldGVfbWVzc2Fn' + 'ZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGAQgASgDSAJSIm5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaW' + 'xsaXNlY29uZHOIAQFCFgoUX2FmZmVjdGVkX2NvbnRhY3RfaWRCEQoPX25ld19ncm91cF9uYW1l' + 'QikKJ19uZXdfZGVsZXRlX21lc3NhZ2VzX2FmdGVyX21pbGxpc2Vjb25kcxqvAQoLVGV4dE1lc3' + 'NhZ2USKgoRc2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0' + 'GAIgASgJUgR0ZXh0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEi0KEHF1b3RlX21lc3' + 'NhZ2VfaWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFCEwoRX3F1b3RlX21lc3NhZ2VfaWQa' + 'zgEKFUFkZGl0aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2' + 'VuZGVyTWVzc2FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJdGltZXN0YW1wEhIKBHR5cGUYAyAB' + 'KAlSBHR5cGUSOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYBCABKAxIAFIVYWRkaXRpb25hbE' + '1lc3NhZ2VEYXRhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRpkCghSZWFjdGlvbhIq' + 'ChF0YXJnZXRfbWVzc2FnZV9pZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEhQKBWVtb2ppGAIgAS' + 'gJUgVlbW9qaRIWCgZyZW1vdmUYAyABKAhSBnJlbW92ZRq+AgoNTWVzc2FnZVVwZGF0ZRI4CgR0' + 'eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5NZXNzYWdlVXBkYXRlLlR5cGVSBHR5cGUSLw' + 'oRc2VuZGVyX21lc3NhZ2VfaWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlkiAEBEj0KG211bHRp' + 'cGxlX3RhcmdldF9tZXNzYWdlX2lkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYWdlSWRzEh' + 'cKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVzdGFtcCIt' + 'CgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhQKEl9zZW5kZX' + 'JfbWVzc2FnZV9pZEIHCgVfdGV4dBqFBgoFTWVkaWESKgoRc2VuZGVyX21lc3NhZ2VfaWQYASAB' + 'KAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGAIgASgOMhwuRW5jcnlwdGVkQ29udGVudC5NZW' + 'RpYS5UeXBlUgR0eXBlEkYKHWRpc3BsYXlfbGltaXRfaW5fbWlsbGlzZWNvbmRzGAMgASgDSABS' + 'GmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEjcKF3JlcXVpcmVzX2F1dGhlbnRpY2F0aW' + '9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGlt' + 'ZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2VfaWQYBiABKAlIAVIOcXVvdGVNZXNzYWdlSWSIAQESKg' + 'oOZG93bmxvYWRfdG9rZW4YByABKAxIAlINZG93bmxvYWRUb2tlbogBARIqCg5lbmNyeXB0aW9u' + 'X2tleRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEioKDmVuY3J5cHRpb25fbWFjGAkgASgMSA' + 'RSDWVuY3J5cHRpb25NYWOIAQESLgoQZW5jcnlwdGlvbl9ub25jZRgKIAEoDEgFUg9lbmNyeXB0' + 'aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYCyABKAxIBlIVYWRkaXRpb2' + '5hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJ' + 'REVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIgCh5fZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY2' + '9uZHNCEwoRX3F1b3RlX21lc3NhZ2VfaWRCEQoPX2Rvd25sb2FkX3Rva2VuQhEKD19lbmNyeXB0' + 'aW9uX2tleUIRCg9fZW5jcnlwdGlvbl9tYWNCEwoRX2VuY3J5cHRpb25fbm9uY2VCGgoYX2FkZG' + 'l0aW9uYWxfbWVzc2FnZV9kYXRhGqkBCgtNZWRpYVVwZGF0ZRI2CgR0eXBlGAEgASgOMiIuRW5j' + 'cnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEioKEXRhcmdldF9tZXNzYWdlX2' + 'lkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JF' + 'RBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgAS' + 'gOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUS' + 'CwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGqQCCg1Db250YWN0VXBkYXRlEj' + 'gKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlw' + 'ZRI3ChVhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZI' + 'gBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgBARImCgxkaXNwbGF5X25hbWUYBCAB' + 'KAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCGA' + 'oWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5hbWVCDwoNX2Rpc3BsYXlfbmFtZRrZ' + 'AQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cHRlZENvbnRlbnQuUHVzaEtleXMuVH' + 'lwZVIEdHlwZRIaCgZrZXlfaWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgASgMSAFSA2tl' + 'eYgBARIiCgpjcmVhdGVkX2F0GAQgASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUV' + 'VFU1QQABIKCgZVUERBVEUQAUIJCgdfa2V5X2lkQgYKBF9rZXlCDQoLX2NyZWF0ZWRfYXQarwEK' + 'CUZsYW1lU3luYxIjCg1mbGFtZV9jb3VudGVyGAEgASgDUgxmbGFtZUNvdW50ZXISOQoZbGFzdF' + '9mbGFtZV9jb3VudGVyX2NoYW5nZRgCIAEoA1IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIfCgti' + 'ZXN0X2ZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZBIhCgxmb3JjZV91cGRhdGUYBCABKAhSC2Zvcm' + 'NlVXBkYXRlGk0KD1R5cGluZ0luZGljYXRvchIbCglpc190eXBpbmcYASABKAhSCGlzVHlwaW5n' + 'Eh0KCmNyZWF0ZWRfYXQYAiABKANSCWNyZWF0ZWRBdBo/ChRVc2VyRGlzY292ZXJ5UmVxdWVzdB' + 'InCg9jdXJyZW50X3ZlcnNpb24YASABKAxSDmN1cnJlbnRWZXJzaW9uGjEKE1VzZXJEaXNjb3Zl' + 'cnlVcGRhdGUSGgoIbWVzc2FnZXMYASADKAxSCG1lc3NhZ2VzGj0KFEtleVZlcmlmaWNhdGlvbl' + 'Byb29mEiUKDmNhbGN1bGF0ZWRfbWFjGAEgASgMUg1jYWxjdWxhdGVkTWFjQgsKCV9ncm91cF9p' + 'ZEIRCg9faXNfZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm9maWxlX2NvdW50ZXJCIAoeX3Nlbm' + 'Rlcl91c2VyX2Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tfZm9yX2ZyaWVuZF9wcm9tb3Rpb25z' + 'QhEKD19tZXNzYWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX21lZGlhX3VwZGF0ZUIRCg9fY29udG' + 'FjdF91cGRhdGVCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtfZmxhbWVfc3luY0IMCgpfcHVzaF9r' + 'ZXlzQgsKCV9yZWFjdGlvbkIPCg1fdGV4dF9tZXNzYWdlQg8KDV9ncm91cF9jcmVhdGVCDQoLX2' + 'dyb3VwX2pvaW5CDwoNX2dyb3VwX3VwZGF0ZUIaChhfcmVzZW5kX2dyb3VwX3B1YmxpY19rZXlC' + 'EQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFfbWVzc2FnZUITChFfdHlwaW' + '5nX2luZGljYXRvckIZChdfdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdEIYChZfdXNlcl9kaXNjb3Zl' + 'cnlfdXBkYXRlQhkKF19rZXlfdmVyaWZpY2F0aW9uX3Byb29m'); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index 0e973b31..e9ed3067 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -64,6 +64,7 @@ message EncryptedContent { ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD = 0; UNKNOWN_MESSAGE_TYPE = 2; SESSION_OUT_OF_SYNC = 3; + GROUP_NOT_FOUND_OR_NOT_A_MEMBER = 4; } Type type = 1; string related_receipt_id = 2; @@ -71,8 +72,9 @@ message EncryptedContent { message GroupCreate { // key for the state stored on the server - bytes state_key = 3; - bytes group_public_key = 4; + bytes state_key = 3; + bytes group_public_key = 4; + optional string group_name = 5; } message GroupJoin { diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart index 6aa093a2..2079475e 100644 --- a/lib/src/services/api/client2client/errors.c2c.dart +++ b/lib/src/services/api/client2client/errors.c2c.dart @@ -1,15 +1,22 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart' show Value; +import 'package:fixnum/fixnum.dart' show Int64; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' + show IdentityKeyPair; import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'; +import 'package:twonly/src/services/api/messages.api.dart' + show sendCipherText, tryToSendCompleteMessage; +import 'package:twonly/src/services/group.service.dart' show fetchGroupState; import 'package:twonly/src/utils/log.dart'; Future handleErrorMessage( int fromUserId, EncryptedContent_ErrorMessages error, - String receiptId, -) async { + String receiptId, { + String? groupId, +}) async { Log.error('[$receiptId] Got error from $fromUserId: $error'); switch (error.type) { @@ -29,6 +36,59 @@ Future handleErrorMessage( ); case EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC: break; // The other user initiated a new signal session, so ignore the error in this case, as the new session works... + case EncryptedContent_ErrorMessages_Type.GROUP_NOT_FOUND_OR_NOT_A_MEMBER: + if (groupId == null) { + Log.error( + '[$receiptId] GROUP_NOT_FOUND_OR_NOT_A_MEMBER error received, but groupId is null.', + ); + return; + } + final group = await twonlyDB.groupsDao.getGroup(groupId); + if (group == null) { + Log.error( + '[$receiptId] GROUP_NOT_FOUND_OR_NOT_A_MEMBER error received, but group $groupId is not found in database.', + ); + return; + } + // Update group state from the server to ensure the user is still part of the group... + final updatedState = await fetchGroupState(group); + if (updatedState != null) { + final (_, state) = updatedState; + final isStillMember = state.memberIds.contains(Int64(fromUserId)); + if (isStillMember) { + final keyPair = IdentityKeyPair.fromSerialized( + group.myGroupPrivateKey!, + ); + await sendCipherText( + fromUserId, + EncryptedContent( + groupId: groupId, + groupCreate: EncryptedContent_GroupCreate( + stateKey: group.stateEncryptionKey, + groupPublicKey: keyPair.getPublicKey().serialize(), + ), + ), + ); + } + final r = await twonlyDB.receiptsDao.getReceiptById( + error.relatedReceiptId, + ); + if (r != null) { + await twonlyDB.receiptsDao.updateReceiptWidthUserId( + fromUserId, + error.relatedReceiptId, + ReceiptsCompanion( + markForRetry: Value(clock.now()), + retryCount: Value(r.retryCount + 1), + ), + ); + } + // then resend: error.relatedReceiptId + await tryToSendCompleteMessage( + receiptId: error.relatedReceiptId, + blocking: false, + ); + } else {} // ignore: no_default_cases default: break; diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index 324af230..b4021a7a 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -17,7 +17,9 @@ Future handleGroupCreate( EncryptedContent_GroupCreate newGroup, String receiptId, ) async { - final user = await twonlyDB.contactsDao.getContactByUserId(fromUserId).getSingleOrNull(); + final user = await twonlyDB.contactsDao + .getContactByUserId(fromUserId) + .getSingleOrNull(); if (user == null) { // Only contacts can invite other contacts, so this can (via the UI) not happen. Log.error( @@ -43,11 +45,26 @@ Future handleGroupCreate( stateVersionId: const Value(0), stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)), myGroupPrivateKey: Value(myGroupKey.serialize()), - groupName: const Value(''), + groupName: Value(newGroup.hasGroupName() ? newGroup.groupName : ''), joinedGroup: const Value(false), ), ); } else { + // In this case make a group state update and check if the fromUserId is still a admin. otherwise return with an log error message + final updatedState = await fetchGroupState(group); + if (updatedState == null) { + Log.error( + '[$receiptId] Received group invite/create for $groupId, but failed to fetch group state from server.', + ); + return; + } + final (_, state) = updatedState; + if (!state.adminIds.any((id) => id.toInt() == fromUserId)) { + Log.error( + '[$receiptId] Received group invite/create for $groupId from $fromUserId, but they are not an admin of this group.', + ); + return; + } // User was already in the group, so update leftGroup back to false await twonlyDB.groupsDao.updateGroup( groupId, @@ -55,7 +72,6 @@ Future handleGroupCreate( stateVersionId: const Value(0), stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)), myGroupPrivateKey: Value(myGroupKey.serialize()), - groupName: const Value(''), joinedGroup: const Value(false), leftGroup: const Value(false), deletedContent: const Value(false), @@ -79,18 +95,8 @@ Future handleGroupCreate( ), ); - await twonlyDB.groupsDao.insertOrUpdateGroupMember( - GroupMembersCompanion( - groupId: Value(groupId), - contactId: Value(fromUserId), - memberState: const Value( - MemberState.admin, // is the group creator, so must be admin... - ), - groupPublicKey: Value(Uint8List.fromList(newGroup.groupPublicKey)), - ), - ); - - // can be done in the background -> websocket message can be ACK + // Load group members from the server as this is the single source of truth. + // This can be done in the background, so the WebSocket message can be ACKed. unawaited(fetchGroupStatesForUnjoinedGroups()); await sendCipherTextToGroup( @@ -113,7 +119,9 @@ Future handleGroupUpdate( final actionType = groupActionTypeFromString(update.groupActionType); if (actionType == null) { - Log.error('[$receiptId] Group action ${update.groupActionType} is unknown ignoring.'); + Log.error( + '[$receiptId] Group action ${update.groupActionType} is unknown ignoring.', + ); return; } diff --git a/lib/src/services/api/messages.api.dart b/lib/src/services/api/messages.api.dart index 58051256..19333cc5 100644 --- a/lib/src/services/api/messages.api.dart +++ b/lib/src/services/api/messages.api.dart @@ -177,7 +177,17 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({ Uint8List.fromList(message.encryptedContent), ); if (cipherText == null) { - Log.error('Could not encrypt the message. Aborting and trying again.'); + Log.error( + '[${receipt.receiptId}] Could not encrypt the message for user ${receipt.contactId}. Aborting and trying again.', + ); + if (receipt.messageId != null) { + await twonlyDB.messagesDao.handleMessageAckByServer( + receipt.contactId, + receipt.messageId!, + clock.now(), + ); + } + await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); return null; } message.encryptedContent = cipherText.serialize(); @@ -435,7 +445,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact( contactId, ); - if (openReceipts > 6) { + if (openReceipts > 10) { // this prevents that these types of messages are send in case the receiver is offline return null; } diff --git a/lib/src/services/api/server_messages.api.dart b/lib/src/services/api/server_messages.api.dart index ff41f13c..1a3b1275 100644 --- a/lib/src/services/api/server_messages.api.dart +++ b/lib/src/services/api/server_messages.api.dart @@ -211,6 +211,8 @@ Future _handleClient2ClientMessage( type: Message_Type.CIPHERTEXT, encryptedContent: encryptedContent.writeToBuffer(), ); + // Use Value.absent() for CIPHERTEXT messages so that insertReceipt generates a new UUID. + // This prevents receipt ID collisions and ensures the recipient's ACK is tracked correctly. receiptIdDB = const Value.absent(); } else { // Message was successful processed @@ -219,8 +221,9 @@ Future _handleClient2ClientMessage( response ??= Message(type: Message_Type.SENDER_DELIVERY_RECEIPT); + String? targetReceiptId; try { - await twonlyDB.receiptsDao.insertReceipt( + final inserted = await twonlyDB.receiptsDao.insertReceipt( ReceiptsCompanion( receiptId: receiptIdDB ?? Value(receiptId), contactId: Value(fromUserId), @@ -228,10 +231,18 @@ Future _handleClient2ClientMessage( contactWillSendsReceipt: const Value(false), ), ); + // Use the inserted receipt's ID because for CIPHERTEXT messages we generate a new UUID + // (receiptIdDB is Value.absent()) to avoid ID collisions and properly track individual ACKs. + targetReceiptId = inserted?.receiptId; } catch (e) { Log.warn('[$receiptId] Error inserting receipt: $e'); } - await tryToSendCompleteMessage(receiptId: receiptId, blocking: false); + if (targetReceiptId != null) { + await tryToSendCompleteMessage( + receiptId: targetReceiptId, + blocking: false, + ); + } } case Message_Type.TEST_NOTIFICATION: break; @@ -350,6 +361,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( fromUserId, content.errorMessages, receiptId, + groupId: content.hasGroupId() ? content.groupId : null, ); return (null, null); } @@ -430,6 +442,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( /// Verify that the user is (still) in that group... if (!await twonlyDB.groupsDao.isContactInGroup(fromUserId, content.groupId)) { + // Check if this is a direct chat... if (getUUIDforDirectChat(userService.currentUser.userId, fromUserId) == content.groupId) { final contact = await twonlyDB.contactsDao @@ -477,9 +490,19 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( } Log.error( - '[$receiptId] User $fromUserId tried to access group ${content.groupId}.', + '[$receiptId] User $fromUserId tried to access group ${content.groupId}. Sending GROUP_NOT_FOUND_OR_NOT_A_MEMBER error.', + ); + return ( + EncryptedContent( + groupId: content.groupId, + errorMessages: EncryptedContent_ErrorMessages( + type: EncryptedContent_ErrorMessages_Type + .GROUP_NOT_FOUND_OR_NOT_A_MEMBER, + relatedReceiptId: receiptId, + ), + ), + null, ); - return (null, null); } } diff --git a/lib/src/services/group.service.dart b/lib/src/services/group.service.dart index 5df5828d..6e933fe3 100644 --- a/lib/src/services/group.service.dart +++ b/lib/src/services/group.service.dart @@ -139,6 +139,7 @@ Future createNewGroup(String groupName, List members) async { groupCreate: EncryptedContent_GroupCreate( stateKey: stateEncryptionKey, groupPublicKey: myGroupKey.getPublicKey().serialize(), + groupName: group.groupName, ), ), ); @@ -770,6 +771,7 @@ Future addNewGroupMembers( groupCreate: EncryptedContent_GroupCreate( stateKey: group.stateEncryptionKey, groupPublicKey: keyPair.getPublicKey().serialize(), + groupName: group.groupName, ), ), ); diff --git a/lib/src/services/signal/encryption.signal.dart b/lib/src/services/signal/encryption.signal.dart index facf5fc3..f13c9960 100644 --- a/lib/src/services/signal/encryption.signal.dart +++ b/lib/src/services/signal/encryption.signal.dart @@ -29,7 +29,7 @@ Future _signalEncryptMessage( final session = SessionCipher.fromStore(signalStore, address); return await session.encrypt(plaintextContent); } catch (e) { - Log.error(e.toString()); + Log.error('Could not encrypt message for target $target: $e'); return null; } } diff --git a/lib/src/visual/context_menu/group.context_menu.dart b/lib/src/visual/context_menu/group.context_menu.dart index 0e86fc59..02fe6a17 100644 --- a/lib/src/visual/context_menu/group.context_menu.dart +++ b/lib/src/visual/context_menu/group.context_menu.dart @@ -78,12 +78,13 @@ class GroupContextMenu extends StatelessWidget { if (group.isDirectChat) { await twonlyDB.groupsDao.deleteGroup(group.groupId); } else { - await twonlyDB.groupsDao.updateGroup( - group.groupId, - const GroupsCompanion( - deletedContent: Value(true), - ), - ); + await twonlyDB.groupsDao.deleteGroup(group.groupId); + // await twonlyDB.groupsDao.updateGroup( + // group.groupId, + // const GroupsCompanion( + // deletedContent: Value(true), + // ), + // ); } } }, diff --git a/lib/src/visual/views/chats/chat_messages_components/entries/chat_contacts.entry.dart b/lib/src/visual/views/chats/chat_messages_components/entries/chat_contacts.entry.dart index 6deeb1ff..db2b3f2c 100644 --- a/lib/src/visual/views/chats/chat_messages_components/entries/chat_contacts.entry.dart +++ b/lib/src/visual/views/chats/chat_messages_components/entries/chat_contacts.entry.dart @@ -141,13 +141,13 @@ class _ContactRowState extends State<_ContactRow> { ), ); + if (added > 0) await importSignalContactAndCreateRequest(userdata); + await KeyVerificationService.verifySharedContact( contactId: userdata.userId.toInt(), sharedPublicIdentityKey: widget.contact.publicIdentityKey, senderId: widget.message.senderId!, ); - - if (added > 0) await importSignalContactAndCreateRequest(userdata); } catch (e) { Log.error(e); } finally { diff --git a/lib/src/visual/views/chats/chat_messages_components/typing_indicator.dart b/lib/src/visual/views/chats/chat_messages_components/typing_indicator.dart index b0fd81b3..2a34cb3a 100644 --- a/lib/src/visual/views/chats/chat_messages_components/typing_indicator.dart +++ b/lib/src/visual/views/chats/chat_messages_components/typing_indicator.dart @@ -45,7 +45,7 @@ class _TypingIndicatorState extends State { StreamSubscription>? membersSub; Timer? _periodicUpdate; - bool _wasShownOnce = false; + double _wasShownOnce = 0; @override void initState() { @@ -81,19 +81,22 @@ class _TypingIndicatorState extends State { @override Widget build(BuildContext context) { if (_groupMembers.isEmpty) { - return SizedBox(height: _wasShownOnce ? 19 : 0); - } else { - _wasShownOnce = true; + return SizedBox(height: _wasShownOnce); } + final height = + (widget.group.isDirectChat ? 20 : 24) * _groupMembers.length.toDouble(); + _wasShownOnce = height; + return SizedBox( - height: 19, + height: height, child: Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(left: 12), - child: Row( + child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: _groupMembers .map( (member) => Padding( diff --git a/pubspec.lock b/pubspec.lock index 6fb8bcbf..6ed33552 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,7 +65,7 @@ packages: source: hosted version: "1.0.4" archive: - dependency: "direct main" + dependency: transitive description: name: archive sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff @@ -389,18 +389,18 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd + sha256: "6a642e1daa10190af89ba6cb6386c0df7d071a3592080bfe1e44faa63ae1df65" url: "https://pub.dev" source: hosted - version: "12.4.0" + version: "13.1.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + sha256: "04b173a92e2d9161dfead145667037c8d834db725ce2e7b942bfe18fd2f45a46" url: "https://pub.dev" source: hosted - version: "7.0.3" + version: "8.1.0" dots_indicator: dependency: "direct overridden" description: @@ -471,6 +471,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -479,14 +487,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" - url: "https://pub.dev" - source: hosted - version: "10.3.10" file_selector_linux: dependency: transitive description: @@ -834,10 +834,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.2" flutter_sharing_intent: dependency: "direct main" description: @@ -892,14 +892,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - get: - dependency: "direct main" - description: - name: get - sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" - url: "https://pub.dev" - source: hosted - version: "4.7.3" get_it: dependency: "direct main" description: @@ -1037,10 +1029,10 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: @@ -1381,18 +1373,18 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" + sha256: "4bf625947f6c7713ee242296a682e23e44823c09cf9d79e4f1238923c92db852" url: "https://pub.dev" source: hosted - version: "9.0.1" + version: "10.1.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + sha256: db762cb2f4f25ee60fb6359773861b0f199e00b90d237bd85a76a1e806b46ef4 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "4.1.0" path: dependency: "direct main" description: @@ -1694,18 +1686,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa" + sha256: a857d8b1479250aff6b57a51b2c02d31ca05848d441817c43f1640c885c286c0 url: "https://pub.dev" source: hosted - version: "12.0.2" + version: "13.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + sha256: "7f7ae28cf400d13f811e297ff37742dba83b79e0a6f5dce14eec0248274e6ce9" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.1.0" shared_preferences: dependency: transitive description: @@ -2170,18 +2162,18 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: ba6f4bba816c8d7e3c1580e170f3786d216951cc6b94babc3b814c08d2cb2738 url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.3.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + sha256: "73b1d78920a9d6e03f8b4e43e612b87bf3152a0e5c5e5150267762b7c4116904" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "3.0.3" workmanager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c1d91016..b34a310b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,8 +17,8 @@ dependencies: # Trusted published dart.dev or tools.dart.dev collection: ^1.18.0 fixnum: ^1.1.1 - meta: ^1.17.0 - http: ^1.3.0 + meta: ^1.17.0 # used by overwritten dependencies... + http: ^1.6.0 intl: ^0.20.2 path: ^1.9.0 logging: ^1.3.0 @@ -32,7 +32,7 @@ dependencies: # Trusted publisher flutter.dev camera: ^0.12.0+1 flutter_svg: ^2.0.17 - image_picker: ^1.1.2 + image_picker: ^1.2.2 local_auth: ^3.0.0 path_provider: ^2.1.5 url_launcher: ^6.3.2 @@ -44,10 +44,10 @@ dependencies: # Trusted publisher fluttercommunity.dev connectivity_plus: ^7.0.0 - device_info_plus: ^12.1.0 + device_info_plus: ^13.1.0 font_awesome_flutter: ^11.0.0 - share_plus: ^12.0.0 - package_info_plus: ^9.0.0 + share_plus: ^13.1.0 + package_info_plus: ^10.1.0 workmanager: ^0.9.0+3 @@ -91,14 +91,11 @@ dependencies: # With high download. (But should be checked nonetheless.) app_links: ^7.0.0 # 1.6 mio image: ^4.3.0 # 3.3 mio - archive: ^4.0.7 # 6.5 mio - file_picker: ^10.3.6 # 2 mio - get: ^4.7.2 # 740 k flutter_secure_storage: ^10.3.1 # 1.85 mio permission_handler: ^12.0.0+1 # 2 mio # Not yet checked - audio_waveforms: ^2.0.0 + audio_waveforms: ^2.0.2 avatar_maker: ^0.4.0 background_downloader: ^9.4.0 cached_network_image: ^3.4.1 diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py new file mode 100755 index 00000000..54831ac2 --- /dev/null +++ b/scripts/check_dependencies.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +import os +import re +import sys + +def parse_dependencies(pubspec_path): + deps = [] + in_deps = False + + with open(pubspec_path, 'r', encoding='utf-8') as f: + for line in f: + stripped = line.strip() + if not stripped or stripped.startswith('#'): + continue + + # Detect starting of dependencies or ending + if line[0].isalpha() or line.startswith('dev_dependencies') or line.startswith('dependency_overrides'): + if stripped.startswith('dependencies:'): + in_deps = True + else: + in_deps = False + continue + + if in_deps: + # Matches exactly 2 spaces indentation, followed by package name and colon + match = re.match(r'^ ([a-zA-Z0-9_-]+):', line) + if match: + dep = match.group(1) + # Ignore Flutter SDK built-ins and custom local rust bridge wrapper + if dep not in ['flutter', 'flutter_localizations', 'rust_lib_twonly']: + deps.append(dep) + return deps + +def find_used_packages(root_dir): + used = set() + # Matches: import 'package:package_name/...'; or export 'package:package_name/...'; + pattern = re.compile(r'(?:import|export)\s+[\'"]package:([a-zA-Z0-9_-]+)/') + + for dirpath, _, filenames in os.walk(root_dir): + # Skip hidden directories (.git, .dart_tool, etc.) + if any(part.startswith('.') for part in dirpath.split(os.sep)): + continue + for filename in filenames: + if filename.endswith('.dart'): + filepath = os.path.join(dirpath, filename) + try: + with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: + for line in f: + match = pattern.search(line) + if match: + used.add(match.group(1)) + except Exception: + pass + return used + +def main(): + # Locate project root (assuming script is in scripts/ or root/) + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.abspath(os.path.join(script_dir, '..')) + + pubspec_path = os.path.join(project_root, 'pubspec.yaml') + if not os.path.exists(pubspec_path): + print(f"Error: pubspec.yaml not found at {pubspec_path}") + sys.exit(1) + + print("Parsing dependencies from pubspec.yaml...") + declared_deps = parse_dependencies(pubspec_path) + print(f"Found {len(declared_deps)} runtime dependencies.") + + print("\nScanning codebase for imports/exports in lib/, test/, integration_test/...") + used_in_code = set() + for folder in ['lib', 'test', 'integration_test']: + folder_path = os.path.join(project_root, folder) + if os.path.exists(folder_path): + used_in_code.update(find_used_packages(folder_path)) + + unused_deps = [] + used_deps = [] + + for dep in declared_deps: + if dep in used_in_code: + used_deps.append(dep) + else: + unused_deps.append(dep) + + print("\n" + "=" * 50) + print(" RESULTS") + print("=" * 50) + + if unused_deps: + print(f"\n❌ UNUSED DEPENDENCIES ({len(unused_deps)}):") + for dep in sorted(unused_deps): + print(f" - {dep}") + else: + print("\n✅ All dependencies listed in pubspec.yaml are used in the codebase!") + + print(f"\n✨ USED DEPENDENCIES ({len(used_deps)}):") + for dep in sorted(used_deps): + print(f" - {dep}") + +if __name__ == '__main__': + main()