create context request from context menu

This commit is contained in:
otsmr 2025-11-03 17:28:39 +01:00
parent 0d4ab84f91
commit f5dbf4a12e
15 changed files with 224 additions and 62 deletions

View file

@ -219,6 +219,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
.get(); .get();
} }
Future<List<GroupMember>> getAllGroupMemberWithoutPublicKey() {
final query =
((select(groups)..where((t) => t.isDirectChat.equals(false))).join([
leftOuterJoin(
groupMembers,
groupMembers.groupId.equalsExp(groups.groupId),
),
])
..where(groupMembers.groupPublicKey.isNull()));
return query.map((row) => row.readTable(groupMembers)).get();
}
Future<Group?> getDirectChat(int userId) async { Future<Group?> getDirectChat(int userId) async {
final query = final query =
((select(groups)..where((t) => t.isDirectChat.equals(true))).join([ ((select(groups)..where((t) => t.isDirectChat.equals(true))).join([

View file

@ -714,6 +714,7 @@
"@leaveGroup": {}, "@leaveGroup": {},
"createContactRequest": "Kontaktanfrage erstellen", "createContactRequest": "Kontaktanfrage erstellen",
"@createContactRequest": {}, "@createContactRequest": {},
"contactRequestSend": "Kontakanfrage gesendet",
"makeAdmin": "Zum Admin machen", "makeAdmin": "Zum Admin machen",
"@makeAdmin": {}, "@makeAdmin": {},
"removeAdmin": "Als Admin entfernen", "removeAdmin": "Als Admin entfernen",

View file

@ -528,6 +528,7 @@
"createGroup": "Create group", "createGroup": "Create group",
"leaveGroup": "Leave group", "leaveGroup": "Leave group",
"createContactRequest": "Create contact request", "createContactRequest": "Create contact request",
"contactRequestSend": "Contact request send",
"makeAdmin": "Make admin", "makeAdmin": "Make admin",
"removeAdmin": "Remove as admin", "removeAdmin": "Remove as admin",
"removeFromGroup": "Remove from group", "removeFromGroup": "Remove from group",

View file

@ -2270,6 +2270,12 @@ abstract class AppLocalizations {
/// **'Create contact request'** /// **'Create contact request'**
String get createContactRequest; String get createContactRequest;
/// No description provided for @contactRequestSend.
///
/// In en, this message translates to:
/// **'Contact request send'**
String get contactRequestSend;
/// No description provided for @makeAdmin. /// No description provided for @makeAdmin.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -1202,6 +1202,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get createContactRequest => 'Kontaktanfrage erstellen'; String get createContactRequest => 'Kontaktanfrage erstellen';
@override
String get contactRequestSend => 'Kontakanfrage gesendet';
@override @override
String get makeAdmin => 'Zum Admin machen'; String get makeAdmin => 'Zum Admin machen';

View file

@ -1195,6 +1195,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get createContactRequest => 'Create contact request'; String get createContactRequest => 'Create contact request';
@override
String get contactRequestSend => 'Contact request send';
@override @override
String get makeAdmin => 'Make admin'; String get makeAdmin => 'Make admin';

View file

@ -342,7 +342,7 @@ class EncryptedContent_GroupJoin extends $pb.GeneratedMessage {
factory EncryptedContent_GroupJoin.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); factory EncryptedContent_GroupJoin.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.GroupJoin', createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.GroupJoin', createEmptyInstance: create)
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'groupPublicKey', $pb.PbFieldType.OY, protoName: 'groupPublicKey') ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'groupPublicKey', $pb.PbFieldType.OY, protoName: 'groupPublicKey')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -368,14 +368,46 @@ class EncryptedContent_GroupJoin extends $pb.GeneratedMessage {
static EncryptedContent_GroupJoin? _defaultInstance; static EncryptedContent_GroupJoin? _defaultInstance;
/// key for the state stored on the server /// key for the state stored on the server
@$pb.TagNumber(4) @$pb.TagNumber(1)
$core.List<$core.int> get groupPublicKey => $_getN(0); $core.List<$core.int> get groupPublicKey => $_getN(0);
@$pb.TagNumber(4) @$pb.TagNumber(1)
set groupPublicKey($core.List<$core.int> v) { $_setBytes(0, v); } set groupPublicKey($core.List<$core.int> v) { $_setBytes(0, v); }
@$pb.TagNumber(4) @$pb.TagNumber(1)
$core.bool hasGroupPublicKey() => $_has(0); $core.bool hasGroupPublicKey() => $_has(0);
@$pb.TagNumber(4) @$pb.TagNumber(1)
void clearGroupPublicKey() => clearField(4); void clearGroupPublicKey() => clearField(1);
}
class EncryptedContent_ResendGroupPublicKey extends $pb.GeneratedMessage {
factory EncryptedContent_ResendGroupPublicKey() => create();
EncryptedContent_ResendGroupPublicKey._() : super();
factory EncryptedContent_ResendGroupPublicKey.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EncryptedContent_ResendGroupPublicKey.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.ResendGroupPublicKey', createEmptyInstance: create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EncryptedContent_ResendGroupPublicKey clone() => EncryptedContent_ResendGroupPublicKey()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EncryptedContent_ResendGroupPublicKey copyWith(void Function(EncryptedContent_ResendGroupPublicKey) updates) => super.copyWith((message) => updates(message as EncryptedContent_ResendGroupPublicKey)) as EncryptedContent_ResendGroupPublicKey;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_ResendGroupPublicKey create() => EncryptedContent_ResendGroupPublicKey._();
EncryptedContent_ResendGroupPublicKey createEmptyInstance() => create();
static $pb.PbList<EncryptedContent_ResendGroupPublicKey> createRepeated() => $pb.PbList<EncryptedContent_ResendGroupPublicKey>();
@$core.pragma('dart2js:noInline')
static EncryptedContent_ResendGroupPublicKey getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedContent_ResendGroupPublicKey>(create);
static EncryptedContent_ResendGroupPublicKey? _defaultInstance;
} }
class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage { class EncryptedContent_GroupUpdate extends $pb.GeneratedMessage {
@ -1281,6 +1313,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
EncryptedContent_GroupCreate? groupCreate, EncryptedContent_GroupCreate? groupCreate,
EncryptedContent_GroupJoin? groupJoin, EncryptedContent_GroupJoin? groupJoin,
EncryptedContent_GroupUpdate? groupUpdate, EncryptedContent_GroupUpdate? groupUpdate,
EncryptedContent_ResendGroupPublicKey? resendGroupPublicKey,
}) { }) {
final $result = create(); final $result = create();
if (groupId != null) { if (groupId != null) {
@ -1328,6 +1361,9 @@ class EncryptedContent extends $pb.GeneratedMessage {
if (groupUpdate != null) { if (groupUpdate != null) {
$result.groupUpdate = groupUpdate; $result.groupUpdate = groupUpdate;
} }
if (resendGroupPublicKey != null) {
$result.resendGroupPublicKey = resendGroupPublicKey;
}
return $result; return $result;
} }
EncryptedContent._() : super(); EncryptedContent._() : super();
@ -1350,6 +1386,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
..aOM<EncryptedContent_GroupCreate>(14, _omitFieldNames ? '' : 'groupCreate', protoName: 'groupCreate', subBuilder: EncryptedContent_GroupCreate.create) ..aOM<EncryptedContent_GroupCreate>(14, _omitFieldNames ? '' : 'groupCreate', protoName: 'groupCreate', subBuilder: EncryptedContent_GroupCreate.create)
..aOM<EncryptedContent_GroupJoin>(15, _omitFieldNames ? '' : 'groupJoin', protoName: 'groupJoin', subBuilder: EncryptedContent_GroupJoin.create) ..aOM<EncryptedContent_GroupJoin>(15, _omitFieldNames ? '' : 'groupJoin', protoName: 'groupJoin', subBuilder: EncryptedContent_GroupJoin.create)
..aOM<EncryptedContent_GroupUpdate>(16, _omitFieldNames ? '' : 'groupUpdate', protoName: 'groupUpdate', subBuilder: EncryptedContent_GroupUpdate.create) ..aOM<EncryptedContent_GroupUpdate>(16, _omitFieldNames ? '' : 'groupUpdate', protoName: 'groupUpdate', subBuilder: EncryptedContent_GroupUpdate.create)
..aOM<EncryptedContent_ResendGroupPublicKey>(17, _omitFieldNames ? '' : 'resendGroupPublicKey', protoName: 'resendGroupPublicKey', subBuilder: EncryptedContent_ResendGroupPublicKey.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -1533,6 +1570,17 @@ class EncryptedContent extends $pb.GeneratedMessage {
void clearGroupUpdate() => clearField(16); void clearGroupUpdate() => clearField(16);
@$pb.TagNumber(16) @$pb.TagNumber(16)
EncryptedContent_GroupUpdate ensureGroupUpdate() => $_ensure(14); EncryptedContent_GroupUpdate ensureGroupUpdate() => $_ensure(14);
@$pb.TagNumber(17)
EncryptedContent_ResendGroupPublicKey get resendGroupPublicKey => $_getN(15);
@$pb.TagNumber(17)
set resendGroupPublicKey(EncryptedContent_ResendGroupPublicKey v) { setField(17, v); }
@$pb.TagNumber(17)
$core.bool hasResendGroupPublicKey() => $_has(15);
@$pb.TagNumber(17)
void clearResendGroupPublicKey() => clearField(17);
@$pb.TagNumber(17)
EncryptedContent_ResendGroupPublicKey ensureResendGroupPublicKey() => $_ensure(15);
} }

View file

@ -118,8 +118,9 @@ const EncryptedContent$json = {
{'1': 'groupCreate', '3': 14, '4': 1, '5': 11, '6': '.EncryptedContent.GroupCreate', '9': 12, '10': 'groupCreate', '17': true}, {'1': 'groupCreate', '3': 14, '4': 1, '5': 11, '6': '.EncryptedContent.GroupCreate', '9': 12, '10': 'groupCreate', '17': true},
{'1': 'groupJoin', '3': 15, '4': 1, '5': 11, '6': '.EncryptedContent.GroupJoin', '9': 13, '10': 'groupJoin', '17': true}, {'1': 'groupJoin', '3': 15, '4': 1, '5': 11, '6': '.EncryptedContent.GroupJoin', '9': 13, '10': 'groupJoin', '17': true},
{'1': 'groupUpdate', '3': 16, '4': 1, '5': 11, '6': '.EncryptedContent.GroupUpdate', '9': 14, '10': 'groupUpdate', '17': true}, {'1': 'groupUpdate', '3': 16, '4': 1, '5': 11, '6': '.EncryptedContent.GroupUpdate', '9': 14, '10': 'groupUpdate', '17': true},
{'1': 'resendGroupPublicKey', '3': 17, '4': 1, '5': 11, '6': '.EncryptedContent.ResendGroupPublicKey', '9': 15, '10': 'resendGroupPublicKey', '17': true},
], ],
'3': [EncryptedContent_GroupCreate$json, EncryptedContent_GroupJoin$json, EncryptedContent_GroupUpdate$json, EncryptedContent_TextMessage$json, EncryptedContent_Reaction$json, EncryptedContent_MessageUpdate$json, EncryptedContent_Media$json, EncryptedContent_MediaUpdate$json, EncryptedContent_ContactRequest$json, EncryptedContent_ContactUpdate$json, EncryptedContent_PushKeys$json, EncryptedContent_FlameSync$json], '3': [EncryptedContent_GroupCreate$json, EncryptedContent_GroupJoin$json, EncryptedContent_ResendGroupPublicKey$json, EncryptedContent_GroupUpdate$json, EncryptedContent_TextMessage$json, EncryptedContent_Reaction$json, EncryptedContent_MessageUpdate$json, EncryptedContent_Media$json, EncryptedContent_MediaUpdate$json, EncryptedContent_ContactRequest$json, EncryptedContent_ContactUpdate$json, EncryptedContent_PushKeys$json, EncryptedContent_FlameSync$json],
'8': [ '8': [
{'1': '_groupId'}, {'1': '_groupId'},
{'1': '_isDirectChat'}, {'1': '_isDirectChat'},
@ -136,6 +137,7 @@ const EncryptedContent$json = {
{'1': '_groupCreate'}, {'1': '_groupCreate'},
{'1': '_groupJoin'}, {'1': '_groupJoin'},
{'1': '_groupUpdate'}, {'1': '_groupUpdate'},
{'1': '_resendGroupPublicKey'},
], ],
}; };
@ -152,10 +154,15 @@ const EncryptedContent_GroupCreate$json = {
const EncryptedContent_GroupJoin$json = { const EncryptedContent_GroupJoin$json = {
'1': 'GroupJoin', '1': 'GroupJoin',
'2': [ '2': [
{'1': 'groupPublicKey', '3': 4, '4': 1, '5': 12, '10': 'groupPublicKey'}, {'1': 'groupPublicKey', '3': 1, '4': 1, '5': 12, '10': 'groupPublicKey'},
], ],
}; };
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_ResendGroupPublicKey$json = {
'1': 'ResendGroupPublicKey',
};
@$core.Deprecated('Use encryptedContentDescriptor instead') @$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_GroupUpdate$json = { const EncryptedContent_GroupUpdate$json = {
'1': 'GroupUpdate', '1': 'GroupUpdate',
@ -376,53 +383,56 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'Mh0uRW5jcnlwdGVkQ29udGVudC5Hcm91cENyZWF0ZUgMUgtncm91cENyZWF0ZYgBARI+Cglncm' 'Mh0uRW5jcnlwdGVkQ29udGVudC5Hcm91cENyZWF0ZUgMUgtncm91cENyZWF0ZYgBARI+Cglncm'
'91cEpvaW4YDyABKAsyGy5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwSm9pbkgNUglncm91cEpvaW6I' '91cEpvaW4YDyABKAsyGy5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwSm9pbkgNUglncm91cEpvaW6I'
'AQESRAoLZ3JvdXBVcGRhdGUYECABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSA' 'AQESRAoLZ3JvdXBVcGRhdGUYECABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSA'
'5SC2dyb3VwVXBkYXRliAEBGlEKC0dyb3VwQ3JlYXRlEhoKCHN0YXRlS2V5GAMgASgMUghzdGF0' '5SC2dyb3VwVXBkYXRliAEBEl8KFHJlc2VuZEdyb3VwUHVibGljS2V5GBEgASgLMiYuRW5jcnlw'
'ZUtleRImCg5ncm91cFB1YmxpY0tleRgEIAEoDFIOZ3JvdXBQdWJsaWNLZXkaMwoJR3JvdXBKb2' 'dGVkQ29udGVudC5SZXNlbmRHcm91cFB1YmxpY0tleUgPUhRyZXNlbmRHcm91cFB1YmxpY0tleY'
'luEiYKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRq6AQoLR3JvdXBVcGRh' 'gBARpRCgtHcm91cENyZWF0ZRIaCghzdGF0ZUtleRgDIAEoDFIIc3RhdGVLZXkSJgoOZ3JvdXBQ'
'dGUSKAoPZ3JvdXBBY3Rpb25UeXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMQoRYWZmZWN0ZW' 'dWJsaWNLZXkYBCABKAxSDmdyb3VwUHVibGljS2V5GjMKCUdyb3VwSm9pbhImCg5ncm91cFB1Ym'
'RDb250YWN0SWQYAiABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESJwoMbmV3R3JvdXBOYW1l' 'xpY0tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkaugEK'
'GAMgASgJSAFSDG5ld0dyb3VwTmFtZYgBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25ld0' 'C0dyb3VwVXBkYXRlEigKD2dyb3VwQWN0aW9uVHlwZRgBIAEoCVIPZ3JvdXBBY3Rpb25UeXBlEj'
'dyb3VwTmFtZRqpAQoLVGV4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5k' 'EKEWFmZmVjdGVkQ29udGFjdElkGAIgASgDSABSEWFmZmVjdGVkQ29udGFjdElkiAEBEicKDG5l'
'ZXJNZXNzYWdlSWQSEgoEdGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbW' 'd0dyb3VwTmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQFCFAoSX2FmZmVjdGVkQ29udGFjdE'
'VzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9f' 'lkQg8KDV9uZXdHcm91cE5hbWUaqQEKC1RleHRNZXNzYWdlEigKD3NlbmRlck1lc3NhZ2VJZBgB'
'cXVvdGVNZXNzYWdlSWQaYgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YX' 'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGA'
'JnZXRNZXNzYWdlSWQSFAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVt' 'MgASgDUgl0aW1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBCABKAlIAFIOcXVvdGVNZXNzYWdl'
'b3ZlGrcCCg1NZXNzYWdlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk' 'SWSIAQFCEQoPX3F1b3RlTWVzc2FnZUlkGmIKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJZB'
'1lc3NhZ2VVcGRhdGUuVHlwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2Vu' 'gBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEhQKBWVtb2ppGAIgASgJUgVlbW9qaRIWCgZyZW1vdmUY'
'ZGVyTWVzc2FnZUlkiAEBEjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdG' 'AyABKAhSBnJlbW92ZRq3AgoNTWVzc2FnZVVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdG'
'lwbGVUYXJnZXRNZXNzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3Rh' 'VkQ29udGVudC5NZXNzYWdlVXBkYXRlLlR5cGVSBHR5cGUSLQoPc2VuZGVyTWVzc2FnZUlkGAIg'
'bXAYBSABKANSCXRpbWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEg' 'ASgJSABSD3NlbmRlck1lc3NhZ2VJZIgBARI6ChhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMYAy'
'oKBk9QRU5FRBACQhIKEF9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQajAUKBU1lZGlhEigKD3Nl' 'ADKAlSGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxIXCgR0ZXh0GAQgASgJSAFSBHRleHSIAQES'
'bmRlck1lc3NhZ2VJZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5Fbm' 'HAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXAiLQoEVHlwZRIKCgZERUxFVEUQABINCglFRE'
'NyeXB0ZWRDb250ZW50Lk1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNl' 'lUX1RFWFQQARIKCgZPUEVORUQQAkISChBfc2VuZGVyTWVzc2FnZUlkQgcKBV90ZXh0GowFCgVN'
'Y29uZHMYAyABKANIAFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZX' 'ZWRpYRIoCg9zZW5kZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGA'
'NBdXRoZW50aWNhdGlvbhgEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3Rh' 'IgASgOMhwuRW5jcnlwdGVkQ29udGVudC5NZWRpYS5UeXBlUgR0eXBlEkMKGmRpc3BsYXlMaW1p'
'bXAYBSABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3' 'dEluTWlsbGlzZWNvbmRzGAMgASgDSABSGmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEj'
'NhZ2VJZIgBARIpCg1kb3dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoN' 'YKFnJlcXVpcmVzQXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24S'
'ZW5jcnlwdGlvbktleRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYW' 'HAoJdGltZXN0YW1wGAUgASgDUgl0aW1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBiABKAlIAV'
'MYCSABKAxIBFINZW5jcnlwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIP' 'IOcXVvdGVNZXNzYWdlSWSIAQESKQoNZG93bmxvYWRUb2tlbhgHIAEoDEgCUg1kb3dubG9hZFRv'
'ZW5jcnlwdGlvbk5vbmNliAEBIjMKBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBV' 'a2VuiAEBEikKDWVuY3J5cHRpb25LZXkYCCABKAxIA1INZW5jcnlwdGlvbktleYgBARIpCg1lbm'
'ZJREVPEAISBwoDR0lGEANCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD19xdW90' 'NyeXB0aW9uTWFjGAkgASgMSARSDWVuY3J5cHRpb25NYWOIAQESLQoPZW5jcnlwdGlvbk5vbmNl'
'ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5fZW5jcn' 'GAogASgMSAVSD2VuY3J5cHRpb25Ob25jZYgBASIzCgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU'
'lwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlGqcBCgtNZWRpYVVwZGF0ZRI2CgR0eXBlGAEg' '1BR0UQARIJCgVWSURFTxACEgcKA0dJRhADQh0KG19kaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25k'
'ASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEigKD3RhcmdldE' 'c0IRCg9fcXVvdGVNZXNzYWdlSWRCEAoOX2Rvd25sb2FkVG9rZW5CEAoOX2VuY3J5cHRpb25LZX'
'1lc3NhZ2VJZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDAoIUkVPUEVORUQQABIK' 'lCEAoOX2VuY3J5cHRpb25NYWNCEgoQX2VuY3J5cHRpb25Ob25jZRqnAQoLTWVkaWFVcGRhdGUS'
'CgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVlc3QSOQoEdH' 'NgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGUuVHlwZVIEdHlwZR'
'lwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZVIEdHlwZSIr' 'IoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJF'
'CgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhrwAQoNQ29udGFjdF' 'T1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZX'
'VwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VXBkYXRlLlR5' 'F1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5'
'cGVSBHR5cGUSNQoTYXZhdGFyU3ZnQ29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJTdmdDb21wcm' 'cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIa8A'
'Vzc2VkiAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUS' 'EKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFj'
'CwoHUkVRVUVTVBAAEgoKBlVQREFURRABQhYKFF9hdmF0YXJTdmdDb21wcmVzc2VkQg4KDF9kaX' 'dFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXByZXNzZWQYAiABKAxIAFITYXZhdG'
'NwbGF5TmFtZRrVAQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cHRlZENvbnRlbnQu' 'FyU3ZnQ29tcHJlc3NlZIgBARIlCgtkaXNwbGF5TmFtZRgDIAEoCUgBUgtkaXNwbGF5TmFtZYgB'
'UHVzaEtleXMuVHlwZVIEdHlwZRIZCgVrZXlJZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZXkYAy' 'ASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3'
'ABKAxIAVIDa2V5iAEBEiEKCWNyZWF0ZWRBdBgEIAEoA0gCUgljcmVhdGVkQXSIAQEiHwoEVHlw' 'NlZEIOCgxfZGlzcGxheU5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0'
'ZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCCAoGX2tleUlkQgYKBF9rZXlCDAoKX2NyZWF0ZW' 'ZWRDb250ZW50LlB1c2hLZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQ'
'RBdBqHAQoJRmxhbWVTeW5jEiIKDGZsYW1lQ291bnRlchgBIAEoA1IMZmxhbWVDb3VudGVyEjYK' 'ESFQoDa2V5GAMgASgMSAFSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0'
'Fmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2UYAiABKANSFmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2USHg' 'iAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5Qg'
'oKYmVzdEZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZEIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3RD' 'wKCl9jcmVhdGVkQXQahwEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1l'
'aGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaW' 'Q291bnRlchI2ChZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudG'
'FCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdEIM' 'VyQ2hhbmdlEh4KCmJlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmRCCgoIX2dyb3VwSWRCDwoN'
'CgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdlQg' 'X2lzRGlyZWN0Q2hhdEIXChVfc2VuZGVyUHJvZmlsZUNvdW50ZXJCEAoOX21lc3NhZ2VVcGRhdG'
'4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZQ=='); 'VCCAoGX21lZGlhQg4KDF9tZWRpYVVwZGF0ZUIQCg5fY29udGFjdFVwZGF0ZUIRCg9fY29udGFj'
'dFJlcXVlc3RCDAoKX2ZsYW1lU3luY0ILCglfcHVzaEtleXNCCwoJX3JlYWN0aW9uQg4KDF90ZX'
'h0TWVzc2FnZUIOCgxfZ3JvdXBDcmVhdGVCDAoKX2dyb3VwSm9pbkIOCgxfZ3JvdXBVcGRhdGVC'
'FwoVX3Jlc2VuZEdyb3VwUHVibGljS2V5');

View file

@ -50,6 +50,7 @@ message EncryptedContent {
optional GroupCreate groupCreate = 14; optional GroupCreate groupCreate = 14;
optional GroupJoin groupJoin = 15; optional GroupJoin groupJoin = 15;
optional GroupUpdate groupUpdate = 16; optional GroupUpdate groupUpdate = 16;
optional ResendGroupPublicKey resendGroupPublicKey = 17;
message GroupCreate { message GroupCreate {
@ -60,7 +61,11 @@ message EncryptedContent {
message GroupJoin { message GroupJoin {
// key for the state stored on the server // key for the state stored on the server
bytes groupPublicKey = 4; bytes groupPublicKey = 1;
}
message ResendGroupPublicKey {
} }
message GroupUpdate { message GroupUpdate {

View file

@ -101,6 +101,7 @@ class ApiService {
unawaited(setupNotificationWithUsers()); unawaited(setupNotificationWithUsers());
unawaited(signalHandleNewServerConnection()); unawaited(signalHandleNewServerConnection());
unawaited(fetchGroupStatesForUnjoinedGroups()); unawaited(fetchGroupStatesForUnjoinedGroups());
unawaited(fetchMissingGroupPublicKey());
} }
} }

View file

@ -180,3 +180,22 @@ Future<bool> handleGroupJoin(
); );
return true; return true;
} }
Future<void> handleResendGroupPublicKey(
int fromUserId,
String groupId,
EncryptedContent_GroupJoin join,
) async {
final group = await twonlyDB.groupsDao.getGroup(groupId);
if (group == null || group.myGroupPrivateKey == null) return;
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
await sendCipherText(
fromUserId,
EncryptedContent(
groupId: groupId,
groupJoin: EncryptedContent_GroupJoin(
groupPublicKey: keyPair.getPublicKey().serialize(),
),
),
);
}

View file

@ -288,6 +288,15 @@ Future<PlaintextContent?> handleEncryptedMessage(
return null; return null;
} }
if (content.hasResendGroupPublicKey()) {
await handleResendGroupPublicKey(
fromUserId,
content.groupId,
content.groupJoin,
);
return null;
}
if (content.hasTextMessage()) { if (content.hasTextMessage()) {
await handleTextMessage( await handleTextMessage(
fromUserId, fromUserId,

View file

@ -149,6 +149,25 @@ Future<void> fetchGroupStatesForUnjoinedGroups() async {
} }
} }
Future<void> fetchMissingGroupPublicKey() async {
final members = await twonlyDB.groupsDao.getAllGroupMemberWithoutPublicKey();
for (final member in members) {
if (member.lastMessage == null) continue;
// only request if the users has send a message in the last two days.
if (member.lastMessage!
.isAfter(DateTime.now().subtract(const Duration(days: 2)))) {
await sendCipherText(
member.contactId,
EncryptedContent(
groupId: member.groupId,
resendGroupPublicKey: EncryptedContent_ResendGroupPublicKey(),
),
);
}
}
}
Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
if (group.leftGroup) { if (group.leftGroup) {
Log.error( Log.error(

View file

@ -101,7 +101,6 @@ class _GroupCreateSelectGroupNameViewState
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
final user = widget.selectedUsers[i]; final user = widget.selectedUsers[i];
return UserContextMenu( return UserContextMenu(
key: GlobalKey(),
contact: user, contact: user,
child: ListTile( child: ListTile(
title: Row( title: Row(

View file

@ -1,9 +1,12 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/services/group.services.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart';
@ -85,6 +88,31 @@ class GroupMemberContextMenu extends StatelessWidget {
} }
} }
Future<void> _makeContactRequest(BuildContext context) async {
await twonlyDB.contactsDao.updateContact(
member.contactId,
const ContactsCompanion(
requested: Value(true),
),
);
await sendCipherText(
member.contactId,
EncryptedContent(
contactRequest: EncryptedContent_ContactRequest(
type: EncryptedContent_ContactRequest_Type.REQUEST,
),
),
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.lang.contactRequestSend),
duration: const Duration(seconds: 3),
),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ContextMenu( return ContextMenu(
@ -112,9 +140,7 @@ class GroupMemberContextMenu extends StatelessWidget {
if (!contact.accepted) if (!contact.accepted)
ContextMenuItem( ContextMenuItem(
title: context.lang.createContactRequest, title: context.lang.createContactRequest,
onTap: () async { onTap: () => _makeContactRequest(context),
// onResponseTriggered();
},
icon: FontAwesomeIcons.userPlus, icon: FontAwesomeIcons.userPlus,
), ),
if (member.groupPublicKey != null && if (member.groupPublicKey != null &&