From ea0529ed233e4a352b9fdb8eb0ffaea28a249144 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 7 Feb 2024 01:15:03 +0100 Subject: [PATCH 001/115] --- 888 --- 6.1.1rc1 From dd18f0c663bc13f6de4bef0a5fe6769858998e60 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 7 Feb 2024 02:07:49 +0100 Subject: [PATCH 002/115] Fix freeze race condition, part 1 Timers have to be reworked to be freezable, too (that will be part 2) --- Monal/Classes/xmpp.h | 1 + Monal/Classes/xmpp.m | 94 +++++++++---------- .../NotificationService/NotificationService.m | 4 +- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/Monal/Classes/xmpp.h b/Monal/Classes/xmpp.h index 5aae41bad2..158380c27e 100644 --- a/Monal/Classes/xmpp.h +++ b/Monal/Classes/xmpp.h @@ -22,6 +22,7 @@ typedef NS_ENUM (NSInteger, xmppState) { kStateLoggedOut = -1, kStateDisconnected, // has connected once kStateReconnecting, + kStateConnected, kStateHasStream, kStateLoggedIn, kStateBinding, diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index f52a35550c..4ccd098cec 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -718,10 +718,11 @@ -(void) freezeParseQueue //this has to be synchronous because we want to be sure no further stanzas are leaking from the parse queue //into the receive queue once we leave this method _parseQueue.suspended = YES; - MLAssert([self parseQueueFrozen] == YES, @"Parse queue not frozen after setting suspended to YES!"); [self dispatchOnReceiveQueue: ^{ + MLAssert([self parseQueueFrozen] == YES, @"Parse queue not frozen after setting suspended to YES (in receive queue)!"); DDLogWarn(@"Parse queue is frozen now!"); }]; + MLAssert([self parseQueueFrozen] == YES, @"Parse queue not frozen after setting suspended to YES!"); } -(void) unfreezeParseQueue @@ -830,6 +831,12 @@ -(void) connect if([[HelperTools defaultsDB] boolForKey:@"AutodeleteAllMessagesAfter3Days"]) [[DataLayer sharedInstance] autodeleteAllMessagesAfter3Days]; + if(_parseQueue.suspended) + { + DDLogWarn(@"Not trying to connect: parse queue frozen!"); + return; + } + [self dispatchAsyncOnReceiveQueue: ^{ [self->_parseQueue cancelAllOperations]; //throw away all parsed but not processed stanzas from old connections [self unfreezeParseQueue]; //make sure the parse queue is operational again @@ -1158,7 +1165,6 @@ -(void) closeSocket self->_accountState = kStateDisconnected; [self->_parseQueue cancelAllOperations]; //throw away all parsed but not processed stanzas (we should have closed sockets then!) - [self unfreezeParseQueue]; //make sure the cancelled operations get handled and our next connect can use the parse queue again //we don't throw away operations in the receive queue because they could be more than just stanzas //(for example outgoing messages that should be written to the smacks queue instead of just vanishing in a void) //all incoming stanzas in the receive queue will honor the _accountState being lower than kStateReconnecting and be dropped @@ -1219,8 +1225,11 @@ -(void) reconnectWithStreamError:(MLXMLNode* _Nullable) streamError andWaitingTi self->_reconnectInProgress = NO; }]; }), (^{ - DDLogInfo(@"Reconnect got aborted..."); - self->_reconnectInProgress = NO; + DDLogInfo(@"Reconnect got aborted: %@", self); + self->_cancelReconnectTimer = nil; + [self dispatchAsyncOnReceiveQueue: ^{ + self->_reconnectInProgress = NO; + }]; })); DDLogInfo(@"reconnect exits"); }]; @@ -1244,47 +1253,30 @@ -(void) prepareXMPPParser _baseParserDelegate = [[MLBasePaser alloc] initWithCompletion:^(MLXMLNode* _Nullable parsedStanza) { DDLogVerbose(@"Parse finished for new <%@> stanza...", parsedStanza.element); - if(!appex) - { - //don't parse any more if we reached > 50 stanzas already parsed and waiting in parse queue - //this makes ure we don't need to much memory while parsing a flood of stanzas and, in theory, - //should create a backpressure ino the tcp stream, too - //the calculated sleep time gives every stanza in the queue ~10ms to be handled - BOOL wasSleeping = NO; - while([self->_parseQueue operationCount] > 50 && self.accountState >= kStateReconnecting) - { - double waittime = (double)[self->_parseQueue operationCount] / 100.0; - DDLogInfo(@"Sleeping %f seconds because parse queue has %lu entries (used/available memory: %.3fMiB / %.3fMiB)...", waittime, (unsigned long)[self->_parseQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); - [NSThread sleepForTimeInterval:waittime]; - wasSleeping = YES; - } - if(wasSleeping) - DDLogInfo(@"Sleeping has ended, parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", (unsigned long)[self->_parseQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); - } - else + //don't parse any more if we reached > 50 stanzas already parsed and waiting in parse queue + //this makes ure we don't need to much memory while parsing a flood of stanzas and, in theory, + //should create a backpressure ino the tcp stream, too + //the calculated sleep time gives every stanza in the queue ~10ms to be handled (based on statistics) + BOOL wasSleeping = NO; + while(self.accountState >= kStateConnected) { - BOOL wasSleeping = NO; - while(self.accountState >= kStateReconnecting) - { - //use a much smaller limit while in appex because memory there is limited to ~32MiB - //like in the mainapp the calculated sleep time gives every stanza in the queue ~10ms to be handled - unsigned long operationCount = [self->_parseQueue operationCount]; - double usedMemory = [HelperTools report_memory]; - if(!(operationCount > 50 || (appex && usedMemory > 16 && operationCount > MAX(2, 24 - usedMemory)))) - break; - - double waittime = (double)[self->_parseQueue operationCount] / 100.0; - DDLogInfo(@"Sleeping %f seconds because parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", waittime, (unsigned long)[self->_parseQueue operationCount], usedMemory, (CGFloat)os_proc_available_memory() / 1048576); - [NSThread sleepForTimeInterval:waittime]; - wasSleeping = YES; - } - if(wasSleeping) - DDLogInfo(@"Sleeping has ended, parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", (unsigned long)[self->_parseQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); + //use a much smaller limit while in appex because memory there is limited to ~32MiB + unsigned long operationCount = [self->_parseQueue operationCount]; + double usedMemory = [HelperTools report_memory]; + if(!(operationCount > 50 || (appex && usedMemory > 16 && operationCount > MAX(2, 24 - usedMemory)))) + break; + + double waittime = (double)[self->_parseQueue operationCount] / 100.0; + DDLogInfo(@"Sleeping %f seconds because parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", waittime, (unsigned long)[self->_parseQueue operationCount], usedMemory, (CGFloat)os_proc_available_memory() / 1048576); + [NSThread sleepForTimeInterval:waittime]; + wasSleeping = YES; } + if(wasSleeping) + DDLogInfo(@"Sleeping has ended, parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", (unsigned long)[self->_parseQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); - if(self.accountState < kStateReconnecting) + if(self.accountState < kStateConnected) { - DDLogWarn(@"Throwing away incoming stanza *before* queueing in parse queue, accountState < kStateReconnecting"); + DDLogWarn(@"Throwing away incoming stanza *before* queueing in parse queue, accountState < kStateConnected"); return; } @@ -1328,9 +1320,9 @@ -(void) prepareXMPPParser //use a synchronous dispatch to make sure no (old) tcp buffers of disconnected connections leak into the receive queue on app unfreeze DDLogVerbose(@"Synchronously handling next stanza on receive queue (%lu stanzas queued in parse queue, %lu current operations in receive queue, %.3fMiB / %.3fMiB memory used / available)", [self->_parseQueue operationCount], [self->_receiveQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); [self->_receiveQueue addOperations:@[[NSBlockOperation blockOperationWithBlock:^{ - if(self.accountState reconnect handlers are frequently used while being bound to schedule a task on *next* (re)connect + //--> in cases where the reconnect handler is only needed if we are not bound, the caller can do this check itself + // (this might introduce small race conditions, though, but these should be negligible in most cases) @synchronized(_reconnectionHandlers) { [_reconnectionHandlers addObject:handler]; } @@ -4649,13 +4645,15 @@ -(void)stream:(NSStream*) stream handleEvent:(NSStreamEvent) eventCode if(_cancelLoginTimer != nil && self->_accountState < kStateLoggedIn) [self reinitLoginTimer]; - if(_blockToCallOnTCPOpen != nil) - { - [self dispatchAsyncOnReceiveQueue:^{ + //we want this to be sync instead of async to make sure we are in kStateConnected before sending anything + [self dispatchOnReceiveQueue:^{ + self->_accountState = kStateConnected; + if(self->_blockToCallOnTCPOpen != nil) + { self->_blockToCallOnTCPOpen(); self->_blockToCallOnTCPOpen = nil; //don't call this twice - }]; - } + } + }]; } break; } diff --git a/Monal/NotificationService/NotificationService.m b/Monal/NotificationService/NotificationService.m index 6bf00d4a4f..40d6de49f7 100644 --- a/Monal/NotificationService/NotificationService.m +++ b/Monal/NotificationService/NotificationService.m @@ -362,9 +362,9 @@ -(void) freezeAllParseQueues { //disconnect to prevent endless loops trying to connect dispatch_async(queue, ^{ - DDLogVerbose(@"freezeAllParseQueues: %@", account); + DDLogVerbose(@"freezing: %@", account); [account freezeParseQueue]; - DDLogVerbose(@"freezeAllParseQueues: %@", account); + DDLogVerbose(@"done freezing: %@", account); }); } dispatch_barrier_sync(queue, ^{ From 94b3305ed926e0565b61c53a6c135094a54f45ec Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 8 Feb 2024 03:00:53 +0100 Subject: [PATCH 003/115] Add version of reporting monal instance to crash reports Some crashes can only be reported after by versions already containing a fix. Adding this version info makes if clear, if the reporting and crashing versions are the same or not. --- Monal/Classes/MLCrashReporter.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Monal/Classes/MLCrashReporter.m b/Monal/Classes/MLCrashReporter.m index e6f888784f..f46ff1bec7 100644 --- a/Monal/Classes/MLCrashReporter.m +++ b/Monal/Classes/MLCrashReporter.m @@ -274,6 +274,9 @@ -(void) filterReports:(NSArray*) reports onCompletion:(KSCrashReportFilterComple { NSMutableString* auxData = [NSMutableString new]; + //add version of monal reporting this crash + [auxData appendString:[NSString stringWithFormat:@"reporterVersion: %@\n", [HelperTools appBuildVersionInfoFor:MLVersionTypeLog]]]; + //add user data to aux data for(NSString* userKey in report[@"user"]) [auxData appendString:[NSString stringWithFormat:@"%@: %@\n", userKey, report[@"user"][userKey]]]; From 5a2ff617e51930d33186defad2c932951bb73872 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 9 Feb 2024 19:30:32 +0100 Subject: [PATCH 004/115] improve error handling --- Monal/Classes/MLIQProcessor.m | 5 +++++ Monal/Classes/MLMucProcessor.m | 14 ++++++-------- Monal/Classes/MonalAppDelegate.m | 10 ++++++++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index 6664e72e2c..8507c8a6d1 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -725,6 +725,11 @@ +(void) iqVersionResult:(XMPPIQ*) iqNode forAccount:(xmpp*) account account.connectionProperties.serverVersion = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:[NSDate date]]; return; } + // TODO Thilo + if(iqNode.fromResource == Nil) + { + return; + } DDLogVerbose(@"Updating software version info for %@", iqNode.from); NSDate* lastInteraction = [[DataLayer sharedInstance] lastInteractionOfJid:iqNode.fromUser andResource:iqNode.fromResource forAccountNo:account.accountNo]; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index bf649ca3c0..44a6b97ada 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -440,11 +440,10 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Got empty room config form for '%@'", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; return; } - //these config options are mandatory and configure the room to be a group --> non anonymous, members only (and persistent) for(NSString* option in mandatoryOptions) { - if(!dataForm[option]) + if([dataForm getField:option] == nil) { DDLogError(@"Could not configure room '%@' to be a groupchat: config option '%@' not available!", roomJid, option); if(deleteOnError) @@ -470,7 +469,7 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma //reconfigure the room dataForm.type = @"submit"; - XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType]; + XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType to:roomJid]; [query setRoomConfig:dataForm]; [_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleRoomConfigResult, handleRoomConfigResultInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; $$ @@ -488,10 +487,6 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma $$ $$instance_handler(handleRoomConfigResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) - MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ - @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], - @"roomJid": [NSString stringWithFormat:@"%@", roomJid], - })); if([iqNode check:@"/"]) { DDLogError(@"Failed to submit room config form of '%@': %@", roomJid, [iqNode findFirst:@"error"]); @@ -503,7 +498,10 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not configure group '%@'", @""), roomJid] forMuc:roomJid withNode:iqNode andIsSevere:YES]; return; } - + MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ + @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], + @"roomJid": [NSString stringWithFormat:@"%@", roomJid], + })); //group is now properly configured and we are joined, but all the code handling a proper join was not run //--> join again to make sure everything is sane [self removeRoomFromCreating:roomJid]; diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index 8bbef63934..d652f3bb5c 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -1617,7 +1617,10 @@ -(void) handleBackgroundProcessingTask:(BGTask*) task [refreshingTask setTaskCompletedWithSuccess:YES]; } - MLAssert([[MLXMPPManager sharedInstance] hasConnectivity], @"BGTASK has *no* connectivity? That's strange!"); + if(![[MLXMPPManager sharedInstance] hasConnectivity]) + { + DDLogError(@"BGTASK has *no* connectivity? That's strange!"); + } [self startBackgroundTimer:BGPROCESS_GRACEFUL_TIMEOUT]; @synchronized(self) { @@ -1715,7 +1718,10 @@ -(void) handleBackgroundRefreshingTask:(BGTask*) task // [[UIApplication sharedApplication] endBackgroundTask:task]; // } - MLAssert([[MLXMPPManager sharedInstance] hasConnectivity], @"BGTASK has *no* connectivity? That's strange!"); + if(![[MLXMPPManager sharedInstance] hasConnectivity]) + { + DDLogError(@"BGTASK has *no* connectivity? That's strange!"); + } [self startBackgroundTimer:GRACEFUL_TIMEOUT]; @synchronized(self) { From 9b808f75b933ddec8f3ebe8bc57cfac79f6dcb4c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 15 Feb 2024 03:09:44 +0100 Subject: [PATCH 005/115] Fix crash on ipad when deleting account/clearing history --- Monal/Classes/XMPPEdit.m | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/XMPPEdit.m b/Monal/Classes/XMPPEdit.m index 0e3373ac14..f0847cd9eb 100644 --- a/Monal/Classes/XMPPEdit.m +++ b/Monal/Classes/XMPPEdit.m @@ -482,7 +482,12 @@ - (IBAction) removeAccountClicked: (id) sender }]; [questionAlert addAction:noAction]; [questionAlert addAction:yesAction]; - questionAlert.popoverPresentationController.sourceView = sender; + + UIPopoverPresentationController* popPresenter = [questionAlert popoverPresentationController]; + if(@available(iOS 16.0, macCatalyst 16.0, *)) + popPresenter.sourceItem = sender; + else + popPresenter.barButtonItem = sender; [self presentViewController:questionAlert animated:YES completion:nil]; } @@ -538,7 +543,12 @@ -(IBAction) deleteAccountClicked:(id) sender }]; [questionAlert addAction:noAction]; [questionAlert addAction:yesAction]; - questionAlert.popoverPresentationController.sourceView = sender; + + UIPopoverPresentationController* popPresenter = [questionAlert popoverPresentationController]; + if(@available(iOS 16.0, macCatalyst 16.0, *)) + popPresenter.sourceItem = sender; + else + popPresenter.barButtonItem = sender; [self presentViewController:questionAlert animated:YES completion:nil]; } @@ -568,7 +578,12 @@ - (IBAction) clearHistoryClicked: (id) sender [questionAlert addAction:noAction]; [questionAlert addAction:yesAction]; - questionAlert.popoverPresentationController.sourceView = sender; + + UIPopoverPresentationController* popPresenter = [questionAlert popoverPresentationController]; + if(@available(iOS 16.0, macCatalyst 16.0, *)) + popPresenter.sourceItem = sender; + else + popPresenter.barButtonItem = sender; [self presentViewController:questionAlert animated:YES completion:nil]; From 12b620b432ebb94482d34b5d5184e026e70d4651 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 15 Feb 2024 19:40:31 +0100 Subject: [PATCH 006/115] Fix crash when disabling encryption on iPad --- Monal/Classes/chatViewController.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 47ad312f29..50be62f579 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -527,18 +527,14 @@ -(void) openCallScreen:(id) sender } } --(IBAction) toggleEncryption:(id)sender +-(IBAction) toggleEncryption:(id) sender { if([HelperTools isContactBlacklistedForEncryption:self.contact]) return; #ifndef DISABLE_OMEMO if(self.contact.isEncrypted) { - NSInteger style = UIAlertControllerStyleActionSheet; -#if TARGET_OS_MACCATALYST - style = UIAlertControllerStyleAlert; -#endif - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Disable encryption?", @"") message:NSLocalizedString(@"Do you really want to disable encryption for this contact?", @"") preferredStyle:style]; + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Disable encryption?", @"") message:NSLocalizedString(@"Do you really want to disable encryption for this contact?", @"") preferredStyle:UIAlertControllerStyleActionSheet]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Yes, deactivate encryption", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [MLChatViewHelper toggleEncryptionForContact:self.contact withSelf:self afterToggle:^() { [self displayEncryptionStateInUI]; @@ -548,7 +544,11 @@ -(IBAction) toggleEncryption:(id)sender [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"No, keep encryption activated", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { [self dismissViewControllerAnimated:YES completion:nil]; }]]; - //alert.popoverPresentationController.sourceView = sender; + UIPopoverPresentationController* popPresenter = [alert popoverPresentationController]; + if(@available(iOS 16.0, macCatalyst 16.0, *)) + popPresenter.sourceItem = sender; + else + popPresenter.barButtonItem = sender; [self presentViewController:alert animated:YES completion:nil]; } else From e94a55d98c87cec7d784eace0f5bee2a038f228c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 15 Feb 2024 20:16:03 +0100 Subject: [PATCH 007/115] Fix regex pattern for geo urls with ';.*' suffix --- Monal/Classes/MLConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/MLConstants.h b/Monal/Classes/MLConstants.h index 717cd64dce..4cbbf0b101 100644 --- a/Monal/Classes/MLConstants.h +++ b/Monal/Classes/MLConstants.h @@ -208,4 +208,4 @@ static inline NSString* _Nonnull LocalizationNotNeeded(NSString* _Nonnull s) //build MLXMLNode query statistics (will only optimize MLXMLNode queries if *not* defined) //#define QueryStatistics 1 -#define geoPattern @"^geo:(-?(?:90|[1-8][0-9]|[0-9])(?:\\.[0-9]{1,32})?),(-?(?:180|1[0-7][0-9]|[0-9]{1,2})(?:\\.[0-9]{1,32})?)$" +#define geoPattern @"^geo:(-?(?:90|[1-8][0-9]|[0-9])(?:\\.[0-9]{1,32})?),(-?(?:180|1[0-7][0-9]|[0-9]{1,2})(?:\\.[0-9]{1,32})?)(;.*)?$" From 7a14070b912f54f3a77e31a30d15367b617e04cf Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 15 Feb 2024 20:33:42 +0100 Subject: [PATCH 008/115] Allow blocklist dialog to be canceled --- Monal/Classes/MLBlockedUsersTableViewController.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Monal/Classes/MLBlockedUsersTableViewController.m b/Monal/Classes/MLBlockedUsersTableViewController.m index 8980b2829f..0a28598d78 100644 --- a/Monal/Classes/MLBlockedUsersTableViewController.m +++ b/Monal/Classes/MLBlockedUsersTableViewController.m @@ -120,7 +120,7 @@ - (IBAction)addBlockButton:(id)sender { passwordField.placeholder = NSLocalizedString(@"user@example.org/resource", @"BlockUserTable - blockJidForm"); }]; - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Block", @"") style:UIAlertActionStyleDefault + [blockJidForm addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Block", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction* action __unused) { NSString* jidToBlock = [blockJidForm textFields][0].text; // try to split the jid @@ -143,9 +143,10 @@ - (IBAction)addBlockButton:(id)sender { [invalidJid addAction:defaultAction]; [self presentViewController:invalidJid animated:YES completion:nil]; } - }]; - - [blockJidForm addAction:defaultAction]; + }]]; + [blockJidForm addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + [blockJidForm dismissViewControllerAnimated:YES completion:nil]; + }]]; [self presentViewController:blockJidForm animated:YES completion:nil]; } @end From 0083b378c3c8cbf452b9313878c80a57c9c00629 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 15 Feb 2024 20:52:46 +0100 Subject: [PATCH 009/115] Present iPad alertSheet using the call button as anchor --- Monal/Classes/ActiveChatsViewController.h | 2 +- Monal/Classes/ActiveChatsViewController.m | 12 ++++++++++-- Monal/Classes/chatViewController.m | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.h b/Monal/Classes/ActiveChatsViewController.h index 92bd62652b..5901631ee1 100644 --- a/Monal/Classes/ActiveChatsViewController.h +++ b/Monal/Classes/ActiveChatsViewController.h @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) UIActivityIndicatorView* spinner; -(void) showCallContactNotFoundAlert:(NSString*) jid; --(void) callContact:(MLContact*) contact; +-(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender; -(void) callContact:(MLContact*) contact withCallType:(MLCallType) callType; -(void) presentAccountPickerForContacts:(NSArray*) contacts andCallType:(MLCallType) callType; -(void) presentCall:(MLCall*) call; diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index 07b1b4aac4..e633ee2572 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -509,7 +509,7 @@ -(void) callContact:(MLContact*) contact withCallType:(MLCallType) callType [self presentCall:[appDelegate.voipProcessor initiateCallWithType:callType toContact:contact]]; } --(void) callContact:(MLContact*) contact +-(void) callContact:(MLContact*) contact withUIKitSender:(_Nullable id) sender { MonalAppDelegate* appDelegate = (MonalAppDelegate *)[[UIApplication sharedApplication] delegate]; MLCall* activeCall = [appDelegate.voipProcessor getActiveCallWithContact:contact]; @@ -530,7 +530,15 @@ -(void) callContact:(MLContact*) contact [self dismissViewControllerAnimated:YES completion:nil]; }]]; UIPopoverPresentationController* popPresenter = [alert popoverPresentationController]; - popPresenter.sourceView = self.view; + if(sender != nil) + { + if(@available(iOS 16.0, macCatalyst 16.0, *)) + popPresenter.sourceItem = sender; + else + popPresenter.barButtonItem = sender; + } + else + popPresenter.sourceView = self.view; [self presentViewController:alert animated:YES completion:nil]; } } diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 50be62f579..ccc9352bd4 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -508,7 +508,7 @@ -(void) openCallScreen:(id) sender //now initiate call MonalAppDelegate* appDelegate = (MonalAppDelegate*)[[UIApplication sharedApplication] delegate]; - [appDelegate.activeChats callContact:self.contact]; + [appDelegate.activeChats callContact:self.contact withUIKitSender:sender]; }]]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { [self dismissViewControllerAnimated:YES completion:nil]; @@ -523,7 +523,7 @@ -(void) openCallScreen:(id) sender else { MonalAppDelegate* appDelegate = (MonalAppDelegate*)[[UIApplication sharedApplication] delegate]; - [appDelegate.activeChats callContact:self.contact]; + [appDelegate.activeChats callContact:self.contact withUIKitSender:sender]; } } From 86697925b97574c897d762077fb4dd75779af83c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 15 Feb 2024 22:09:53 +0100 Subject: [PATCH 010/115] Don't let KSCrash handle exceptions in the simulator --- Monal/Classes/HelperTools.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index a5dda65679..076b012fcb 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -102,7 +102,7 @@ @interface KSCrash() int asyncSafeCopyFile(const char* from, const char* to) { int fd_to, fd_from; - char buf[4096]; + char buf[1024]; ssize_t nread; int saved_errno; @@ -201,9 +201,15 @@ void logException(NSException* exception) void uncaughtExceptionHandler(NSException* exception) { logException(exception); +//don't let kscrash handle the exception if we are in the simulator +//(this makes sure xcode will catch the exception and show proper backtraces etc.) +#if TARGET_OS_SIMULATOR + return; +#else //make sure this crash will be recorded by kscrash using the NSException rather than the c++ exception thrown by the objc runtime //this will make sure that the stacktrace matches the objc exception rather than being a top level c++ stacktrace KSCrash.sharedInstance.uncaughtExceptionHandler(exception); +#endif } //this function will only be in use under macos alpha builds to log every exception (even when catched with @try-@catch constructs) From 4e47373c24a57454b868e05b911127b8ec839a99 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 02:54:09 +0100 Subject: [PATCH 011/115] Improve filtering of messages in non-joined mucs --- Monal/Classes/MLMessageProcessor.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index b2a819ba08..d2784fd9d9 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -242,8 +242,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if(([messageNode check:@"/"] || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { // Ignore all group chat msgs from unkown groups or 1:1 chats - MLContact* mucTestContact = [MLContact createContactFromJid:possibleUnkownContact andAccountNo:account.accountNo]; - if([[DataLayer sharedInstance] isContactInList:messageNode.fromUser forAccount:account.accountNo] == NO || !mucTestContact.isGroup) + if(![[[DataLayer sharedInstance] listMucsForAccount:account.accountNo] containsObject:messageNode.fromUser]) { // ignore message DDLogWarn(@"Ignoring groupchat message from %@", messageNode.toUser); From aeeebc32172438bb603e4e1f29dc683cedc8a629 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 15:45:30 +0100 Subject: [PATCH 012/115] Fix timing issue on creating mucs (idle timer versus really slow iqs) --- Monal/Classes/MLMucProcessor.m | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 44a6b97ada..bf0274dd5e 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -400,6 +400,11 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma } $$instance_handler(handleRoomConfigFormInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(![self isCreating:roomJid]) + { + DDLogError(@"Got room config form invalidation but not creating group, ignoring: %@", roomJid); + return; + } if(deleteOnError) { DDLogError(@"Config form fetch failed, removing muc '%@' from _creating...", roomJid); @@ -412,6 +417,11 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma $$ $$instance_handler(handleRoomConfigForm, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(![self isCreating:iqNode.fromUser]) + { + DDLogError(@"Got room form result but not creating group, ignoring: %@", iqNode); + return; + } MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], @"roomJid": [NSString stringWithFormat:@"%@", roomJid], @@ -475,6 +485,11 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma $$ $$instance_handler(handleRoomConfigResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(![self isCreating:roomJid]) + { + DDLogError(@"Got room config invalidation but not creating group, ignoring: %@", roomJid); + return; + } if(deleteOnError) { DDLogError(@"Config form submit failed, removing muc '%@' from _creating...", roomJid); @@ -487,6 +502,11 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma $$ $$instance_handler(handleRoomConfigResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) + if(![self isCreating:iqNode.fromUser]) + { + DDLogError(@"Got room config result but not creating group, ignoring: %@", iqNode); + return; + } if([iqNode check:@"/"]) { DDLogError(@"Failed to submit room config form of '%@': %@", roomJid, [iqNode findFirst:@"error"]); @@ -530,7 +550,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node } if(![self isCreating:node.fromUser]) { - DDLogError(@"Got 'muc needs configuration' status code (201) without this muc currently being created, ignoring!"); + DDLogError(@"Got 'muc needs configuration' status code (201) without this muc currently being created, ignoring: %@", node.fromUser); break; } @@ -746,9 +766,13 @@ -(void) handleStatusCodes:(XMPPStanza*) node } $$instance_handler(handleCreateTimeout, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, room)) + if(![self isCreating:room]) + { + DDLogError(@"Got room create idle timeout but not creating group, ignoring: %@", room); + return; + } [self removeRoomFromCreating:room]; [self deleteMuc:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could fetch room config form from '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not create group '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; $$ From a404d4154278af1805985396fce6504ee1128e18 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 16:16:20 +0100 Subject: [PATCH 013/115] Autogenerate group names and only call uiHandler once --- Monal/Classes/MLMucProcessor.h | 2 +- Monal/Classes/MLMucProcessor.m | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.h b/Monal/Classes/MLMucProcessor.h index 5161c0da09..8a43f46389 100644 --- a/Monal/Classes/MLMucProcessor.h +++ b/Monal/Classes/MLMucProcessor.h @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN -(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks; //muc management methods --(NSString* _Nullable) createGroup:(NSString*) node; +-(NSString* _Nullable) createGroup:(NSString* _Nullable) node; -(void) changeNameOfMuc:(NSString*) room to:(NSString*) name; -(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject; -(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index bf0274dd5e..ca8a70788c 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -687,6 +687,9 @@ -(void) handleStatusCodes:(XMPPStanza*) node monal_id_block_t uiHandler = [self getUIHandlerForMuc:node.fromUser]; if(uiHandler) { + //remove handler (it will only be called once) + [self removeUIHandlerForMuc:node.fromUser]; + DDLogInfo(@"Calling UI handler for muc %@...", node.fromUser); dispatch_async(dispatch_get_main_queue(), ^{ uiHandler(@{ @@ -776,8 +779,11 @@ -(void) handleStatusCodes:(XMPPStanza*) node [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not create group '%@': timeout", @""), room] forMuc:room withNode:nil andIsSevere:YES]; $$ --(NSString*) createGroup:(NSString*) node +-(NSString* _Nullable) createGroup:(NSString* _Nullable) node { + if(node == nil) + node = [self generateSpeakableGroupNode]; + node = [node stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].lowercaseString; NSString* room = [[NSString stringWithFormat:@"%@@%@", node, _account.connectionProperties.conferenceServer] lowercaseString]; if([[DataLayer sharedInstance] isBuddyMuc:room forAccount:_account.accountNo]) { @@ -1481,4 +1487,20 @@ -(void) logMembersOfMuc:(NSString*) jid } } +-(NSString*) generateSpeakableGroupNode +{ + NSArray* charLists = @[ + @"bcdfghjklmnpqrstvwxyz", + @"aeiou", + ]; + NSMutableString* retval = [NSMutableString new]; + int charTypeBegin = arc4random() % charLists.count; + for(int i=0; i<10; i++) + { + NSString* selectedCharList = charLists[(i + charTypeBegin) % charLists.count]; + [retval appendString:[selectedCharList substringWithRange:NSMakeRange(arc4random() % selectedCharList.length, 1)]]; + } + return retval; +} + @end From 5dd022ba92812adc64bda9b013b5bb15bd590d94 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 17:17:51 +0100 Subject: [PATCH 014/115] hange DataLayer listMucsForAccount to returning an NSSet --- Monal/Classes/DataLayer.h | 2 +- Monal/Classes/DataLayer.m | 7 +++-- Monal/Classes/MLMessageProcessor.m | 2 +- Monal/Classes/MLMucProcessor.m | 12 ++++---- Monal/Classes/MLPubSubProcessor.m | 44 ++++++++++++------------------ 5 files changed, 31 insertions(+), 36 deletions(-) diff --git a/Monal/Classes/DataLayer.h b/Monal/Classes/DataLayer.h index baf68ad8d8..9b879af522 100644 --- a/Monal/Classes/DataLayer.h +++ b/Monal/Classes/DataLayer.h @@ -125,7 +125,7 @@ extern NSString* const kMessageTypeFiletransfer; -(BOOL) updateMucSubject:(NSString*) subject forAccount:(NSNumber*) accountNo andRoom:(NSString*) room; -(NSString*) mucSubjectforAccount:(NSNumber*) accountNo andRoom:(NSString*) room; --(NSMutableArray*) listMucsForAccount:(NSNumber*) accountNo; +-(NSSet*) listMucsForAccount:(NSNumber*) accountNo; -(BOOL) deleteMuc:(NSString*) room forAccountId:(NSNumber*) accountNo; -(void) updateMucTypeTo:(NSString*) type forRoom:(NSString*) room andAccount:(NSNumber*) accountNo; diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index 5ef936ab45..c8d9851187 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -1105,10 +1105,13 @@ -(BOOL) deleteMuc:(NSString*) room forAccountId:(NSNumber*) accountNo }]; } --(NSMutableArray*) listMucsForAccount:(NSNumber*) accountNo +-(NSSet*) listMucsForAccount:(NSNumber*) accountNo { return [self.db idReadTransaction:^{ - return [self.db executeReader:@"SELECT * FROM muc_favorites WHERE account_id=?;" andArguments:@[accountNo]]; + NSMutableSet* retval = [NSMutableSet new]; + for(NSDictionary* entry in [self.db executeReader:@"SELECT * FROM muc_favorites WHERE account_id=?;" andArguments:@[accountNo]]) + [retval addObject:[entry[@"room"] lowercaseString]]; + return retval; }]; } diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index d2784fd9d9..bcfc4f5905 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -241,7 +241,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if(([messageNode check:@"/"] || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { - // Ignore all group chat msgs from unkown groups or 1:1 chats + // Ignore all group chat msgs from unkown groups if(![[[DataLayer sharedInstance] listMucsForAccount:account.accountNo] containsObject:messageNode.fromUser]) { // ignore message diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index ca8a70788c..0f7e829396 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -155,8 +155,8 @@ -(void) handleResourceBound:(NSNotification*) notification } //join MUCs from (current) muc_favorites db, the pending bookmarks fetch will join the remaining currently unknown mucs - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:_account.accountNo]) - [self join:entry[@"room"]]; + for(NSString* room in [[DataLayer sharedInstance] listMucsForAccount:_account.accountNo]) + [self join:room]; } } @@ -887,8 +887,8 @@ -(void) pingAllMucs DDLogInfo(@"Not pinging all mucs, last ping was less than %d seconds ago: %@", MUC_PING, _lastPing); return; } - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:_account.accountNo]) - [self ping:entry[@"room"] withLastPing:_lastPing]; + for(NSString* room in [[DataLayer sharedInstance] listMucsForAccount:_account.accountNo]) + [self ping:room withLastPing:_lastPing]; _lastPing = [NSDate date]; } @@ -1407,8 +1407,8 @@ -(void) updateBookmarks -(BOOL) checkIfStillBookmarked:(NSString*) room { room = [room lowercaseString]; - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:_account.accountNo]) - if([room isEqualToString:[entry[@"room"] lowercaseString]]) + for(NSString* entry in [[DataLayer sharedInstance] listMucsForAccount:_account.accountNo]) + if([room isEqualToString:entry]) return YES; return NO; } diff --git a/Monal/Classes/MLPubSubProcessor.m b/Monal/Classes/MLPubSubProcessor.m index 900a37042f..a49e6db441 100644 --- a/Monal/Classes/MLPubSubProcessor.m +++ b/Monal/Classes/MLPubSubProcessor.m @@ -204,9 +204,7 @@ @implementation MLPubSubProcessor return; } - NSMutableDictionary* ownFavorites = [NSMutableDictionary new]; - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]) - ownFavorites[entry[@"room"]] = entry; + NSSet* ownFavorites = [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]; //new/updated bookmarks if([type isEqualToString:@"publish"]) @@ -226,7 +224,7 @@ @implementation MLPubSubProcessor autojoin = @NO; //default value specified in xep //check if this is a new entry with autojoin=true - if(ownFavorites[room] == nil && [autojoin boolValue]) + if(![ownFavorites containsObject:room] && [autojoin boolValue]) { DDLogInfo(@"Entering muc '%@' on account %@ because it got added to bookmarks...", room, account.accountNo); //make sure we update our favorites table right away, to counter any race conditions when joining multiple mucs with one bookmarks update @@ -238,7 +236,7 @@ @implementation MLPubSubProcessor [account.mucProcessor sendDiscoQueryFor:room withJoin:YES andBookmarksUpdate:NO]; } //check if it is a known entry that changed autojoin to false - else if(ownFavorites[room] != nil && ![autojoin boolValue]) + else if([ownFavorites containsObject:room] && ![autojoin boolValue]) { DDLogInfo(@"Leaving muc '%@' on account %@ because not listed as autojoin=true in bookmarks...", room, account.accountNo); //delete local favorites entry and leave room afterwards @@ -246,7 +244,7 @@ @implementation MLPubSubProcessor [account.mucProcessor leave:room withBookmarksUpdate:NO]; } //check for nickname changes - else if(ownFavorites[room] != nil && nick != nil) + else if([ownFavorites containsObject:room] && nick != nil) { NSString* oldNick = [[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:account.accountNo]; if(![nick isEqualToString:oldNick]) @@ -315,10 +313,8 @@ @implementation MLPubSubProcessor max_items = @"max"; NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary]; - NSMutableDictionary* ownFavorites = [NSMutableDictionary new]; - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]) - ownFavorites[entry[@"room"]] = entry; - DDLogVerbose(@"Own favorites: %@", [ownFavorites allKeys]); + NSSet* ownFavorites = [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]; + DDLogVerbose(@"Own favorites: %@", ownFavorites); //filter passwort protected mucs and make sure jids (the item ids) are always lowercase NSMutableDictionary* _data = [NSMutableDictionary new]; @@ -346,7 +342,7 @@ @implementation MLPubSubProcessor autojoin = @NO; //default value specified in xep //check if the bookmark exists with autojoin==false and only update the autojoin and nick values, if true - if(ownFavorites[room] && ![autojoin boolValue]) + if([ownFavorites containsObject:room] && ![autojoin boolValue]) { DDLogInfo(@"Updating autojoin of bookmarked muc '%@' on account %@ to 'true'...", room, account.accountNo); @@ -372,7 +368,7 @@ @implementation MLPubSubProcessor } //add all mucs not yet listed in bookmarks - NSMutableSet* toAdd = [NSMutableSet setWithArray:[ownFavorites allKeys]]; + NSMutableSet* toAdd = [ownFavorites mutableCopy]; [toAdd minusSet:[NSSet setWithArray:[_data allKeys]]]; for(NSString* room in toAdd) { @@ -402,7 +398,7 @@ @implementation MLPubSubProcessor //remove all mucs not listed in local favorites table NSMutableSet* toRemove = [NSMutableSet setWithArray:[_data allKeys]]; - [toRemove minusSet:[NSSet setWithArray:[ownFavorites allKeys]]]; + [toRemove minusSet:ownFavorites]; for(NSString* room in toRemove) { DDLogInfo(@"Removing muc '%@' on account %@ from bookmarks...", room, account.accountNo); @@ -455,9 +451,7 @@ @implementation MLPubSubProcessor return; } - NSMutableDictionary* ownFavorites = [NSMutableDictionary new]; - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]) - ownFavorites[entry[@"room"]] = entry; + NSSet* ownFavorites = [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]; //new/updated bookmarks if([type isEqualToString:@"publish"]) @@ -489,7 +483,7 @@ @implementation MLPubSubProcessor autojoin = @NO; //default value specified in xep //check if this is a new entry with autojoin=true - if(ownFavorites[room] == nil && [autojoin boolValue]) + if(![ownFavorites containsObject:room] && [autojoin boolValue]) { DDLogInfo(@"Entering muc '%@' on account %@ because it got added to bookmarks...", room, account.accountNo); //make sure we update our favorites table right away, to counter any race conditions when joining multiple mucs with one bookmarks update @@ -501,7 +495,7 @@ @implementation MLPubSubProcessor [account.mucProcessor sendDiscoQueryFor:room withJoin:YES andBookmarksUpdate:NO]; } //check if it is a known entry that changed autojoin to false - else if(ownFavorites[room] != nil && ![autojoin boolValue]) + else if([ownFavorites containsObject:room] && ![autojoin boolValue]) { DDLogInfo(@"Leaving muc '%@' on account %@ because not listed as autojoin=true in bookmarks...", room, account.accountNo); //delete local favorites entry and leave room afterwards @@ -509,7 +503,7 @@ @implementation MLPubSubProcessor [account.mucProcessor leave:room withBookmarksUpdate:NO]; } //check for nickname changes - else if(ownFavorites[room] != nil && nick != nil) + else if([ownFavorites containsObject:room] && nick != nil) { NSString* oldNick = [[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:account.accountNo]; if(![nick isEqualToString:oldNick]) @@ -529,7 +523,7 @@ @implementation MLPubSubProcessor } //remove and leave all mucs removed from bookmarks - NSMutableSet* toLeave = [NSMutableSet setWithArray:[ownFavorites allKeys]]; + NSMutableSet* toLeave = [ownFavorites mutableCopy]; [toLeave minusSet:bookmarkedMucs]; for(NSString* room in toLeave) { @@ -575,9 +569,7 @@ @implementation MLPubSubProcessor } BOOL changed = NO; - NSMutableDictionary* ownFavorites = [NSMutableDictionary new]; - for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]) - ownFavorites[entry[@"room"]] = entry; + NSSet* ownFavorites = [[DataLayer sharedInstance] listMucsForAccount:account.accountNo]; for(NSString* itemId in data) { @@ -606,7 +598,7 @@ @implementation MLPubSubProcessor autojoin = @NO; //default value specified in xep //check if the bookmark exists with autojoin==false and only update the autojoin and nick values, if true - if(ownFavorites[room] && ![autojoin boolValue]) + if([ownFavorites containsObject:room] && ![autojoin boolValue]) { DDLogInfo(@"Updating autojoin of bookmarked muc '%@' on account %@ to 'true'...", room, account.accountNo); @@ -626,7 +618,7 @@ @implementation MLPubSubProcessor } //add all mucs not yet listed in bookmarks - NSMutableSet* toAdd = [NSMutableSet setWithArray:[ownFavorites allKeys]]; + NSMutableSet* toAdd = [ownFavorites mutableCopy]; [toAdd minusSet:bookmarkedMucs]; for(NSString* room in toAdd) { @@ -642,7 +634,7 @@ @implementation MLPubSubProcessor //remove all mucs not listed in local favorites table NSMutableSet* toRemove = [bookmarkedMucs mutableCopy]; - [toRemove minusSet:[NSMutableSet setWithArray:[ownFavorites allKeys]]]; + [toRemove minusSet:ownFavorites]; for(NSString* room in toRemove) { DDLogInfo(@"Removing muc '%@' on account %@ from bookmarks...", room, account.accountNo); From 790ea16dff6516502dd3de2ffe8eec8e403c4237 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:34:06 +0100 Subject: [PATCH 015/115] Create groups UI --- Monal/Classes/ContactEntry.swift | 43 +++++++++ Monal/Classes/ContactPicker.swift | 24 +---- Monal/Classes/ContactsViewController.m | 6 +- Monal/Classes/CreateGroupMenu.swift | 87 ++++++++++--------- Monal/Classes/DataLayer.h | 5 +- Monal/Classes/DataLayer.m | 16 +++- Monal/Classes/MLMucProcessor.h | 1 + Monal/Classes/MLMucProcessor.m | 20 +++++ Monal/Classes/MemberList.swift | 40 ++++----- Monal/Classes/SwiftuiHelpers.swift | 4 +- Monal/Monal.xcodeproj/project.pbxproj | 4 + .../xcschemes/Monal Alpha.xcscheme | 1 - Monal/Podfile.lock | 2 +- Monal/shareSheet-iOS/ShareViewController.m | 4 +- 14 files changed, 153 insertions(+), 104 deletions(-) create mode 100644 Monal/Classes/ContactEntry.swift diff --git a/Monal/Classes/ContactEntry.swift b/Monal/Classes/ContactEntry.swift new file mode 100644 index 0000000000..e159fb182a --- /dev/null +++ b/Monal/Classes/ContactEntry.swift @@ -0,0 +1,43 @@ +// +// ContactEntry.swift +// Monal +// +// Created by Friedrich Altheide on 28.11.23. +// Copyright © 2023 monal-im.org. All rights reserved. +// + +import SwiftUI + +struct ContactEntry: View { + let contact : MLContact + + var body:some View { + ZStack(alignment: .topLeading) { + HStack(alignment: .center) { + Image(uiImage: contact.avatar) + .resizable() + .frame(width: 40, height: 40, alignment: .center) + VStack(alignment: .leading) { + Text(contact.contactDisplayName as String) + Text(contact.contactJid as String).font(.footnote).opacity(0.6) + } + } + } + } +} + +#Preview { + ContactEntry(contact:MLContact.makeDummyContact(0)) +} + +#Preview { + ContactEntry(contact:MLContact.makeDummyContact(1)) +} + +#Preview { + ContactEntry(contact:MLContact.makeDummyContact(2)) +} + +#Preview { + ContactEntry(contact:MLContact.makeDummyContact(3)) +} diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index f7877f8cf6..18dc629085 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -10,24 +10,6 @@ import SwiftUI import monalxmpp import OrderedCollections -struct ContactEntry: View { // TODO move - let contact : MLContact - - var body:some View { - ZStack(alignment: .topLeading) { - HStack(alignment: .center) { - Image(uiImage: contact.avatar) - .resizable() - .frame(width: 40, height: 40, alignment: .center) - VStack(alignment: .leading) { - Text(contact.contactDisplayName as String) - Text(contact.contactJid as String).font(.footnote).opacity(0.6) - } - } - } - } -} - struct ContactPickerEntry: View { let contact : MLContact let isPicked: Bool @@ -57,6 +39,7 @@ struct ContactPicker: View { @Environment(\.presentationMode) private var presentationMode let contacts : [MLContact] + let account : xmpp @Binding var selectedContacts : OrderedSet // already selected when going into the view @State var searchFieldInput = "" @@ -106,8 +89,9 @@ struct ContactPicker: View { } } - init(selectedContacts: Binding>) { + init(account: xmpp, selectedContacts: Binding>) { + self.account = account self._selectedContacts = selectedContacts - self.contacts = DataLayer.sharedInstance().contactList() as! [MLContact] + self.contacts = DataLayer.sharedInstance().possibleGroupMembers(forAccount: account.accountNo) } } diff --git a/Monal/Classes/ContactsViewController.m b/Monal/Classes/ContactsViewController.m index c582966a14..95339d6c64 100644 --- a/Monal/Classes/ContactsViewController.m +++ b/Monal/Classes/ContactsViewController.m @@ -108,14 +108,10 @@ -(void) viewDidLoad addContact.image = [UIImage systemImageNamed:@"person.fill.badge.plus"]; [addContact setAction:@selector(openAddContacts:)]; -#ifdef IS_ALPHA UIBarButtonItem* createGroup = [[UIBarButtonItem alloc] init]; createGroup.image = [UIImage systemImageNamed:@"person.3.fill"]; [createGroup setAction:@selector(openCreateGroup:)]; self.navigationItem.rightBarButtonItems = [[NSArray alloc] initWithObjects:addContact, [[UIBarButtonItem alloc] init], createGroup, nil]; -#else - self.navigationItem.rightBarButtonItems = [[NSArray alloc] initWithObjects:addContact, [UIBarButtonItem new], nil]; -#endif [self configureContactRequestsImage]; @@ -231,7 +227,7 @@ -(void) performSegueWithIdentifier:(NSString*) identifier sender:(id) sender -(void) loadContactsWithFilter:(NSString*) filter { - NSMutableArray* contacts; + NSArray* contacts; if(filter && [filter length] > 0) self.contacts = [[DataLayer sharedInstance] searchContactsWithString:filter]; else diff --git a/Monal/Classes/CreateGroupMenu.swift b/Monal/Classes/CreateGroupMenu.swift index 881d61c1ad..4b69c477bc 100644 --- a/Monal/Classes/CreateGroupMenu.swift +++ b/Monal/Classes/CreateGroupMenu.swift @@ -13,10 +13,10 @@ import monalxmpp import OrderedCollections struct CreateGroupMenu: View { - var delegate: SheetDismisserProtocol + @StateObject private var appDelegate: ObservableKVOWrapper @State private var connectedAccounts: [xmpp] - @State private var selectedAccount: Int + @State private var selectedAccount: xmpp? @State private var groupName: String = "" @State private var showAlert = false @@ -25,42 +25,24 @@ struct CreateGroupMenu: View { @State private var selectedContacts : OrderedSet = [] @ObservedObject private var overlay = LoadingOverlayState() + + private var delegate: SheetDismisserProtocol - @State private var success = false - - private let dismissWithNewGroup: (MLContact) -> () - - init(delegate: SheetDismisserProtocol, dismissWithNewGroup: @escaping (MLContact) -> (), prefillJid: String = "", preauthToken:String? = nil) { - // FIXME + init(delegate: SheetDismisserProtocol) { + _appDelegate = StateObject(wrappedValue: ObservableKVOWrapper(UIApplication.shared.delegate as! MonalAppDelegate)) self.delegate = delegate - self.dismissWithNewGroup = dismissWithNewGroup - self.groupName = prefillJid - // self.preauthToken = preauthToken let connectedAccounts = MLXMPPManager.sharedInstance().connectedXMPP as! [xmpp] self.connectedAccounts = connectedAccounts - self.selectedAccount = connectedAccounts.first != nil ? 0 : -1; + _selectedAccount = State(wrappedValue: connectedAccounts.first) } - // FIXME duplicate code from WelcomeLogIn.swift, maybe move to SwiftuiHelpers - private func errorAlert(title: Text, message: Text = Text("")) { alertPrompt.title = title alertPrompt.message = message showAlert = true } - private func successAlert(title: Text, message: Text) { - alertPrompt.title = title - alertPrompt.message = message - self.success = true // < dismiss entire view on close - showAlert = true - } - - private var buttonColor: Color { - return Color(UIColor.systemBlue) - } - var body: some View { Form { if(connectedAccounts.isEmpty) { @@ -70,22 +52,54 @@ struct CreateGroupMenu: View { else { Section() { - if(connectedAccounts.count > 1) { Picker("Use account", selection: $selectedAccount) { ForEach(Array(self.connectedAccounts.enumerated()), id: \.element) { idx, account in - Text(account.connectionProperties.identity.jid).tag(idx) + Text(account.connectionProperties.identity.jid).tag(account) } } .pickerStyle(.menu) - } TextField(NSLocalizedString("Group Name (optional)", comment: "placeholder when creating new group"), text: $groupName) .autocorrectionDisabled() .autocapitalization(.none) .addClearButton(text:$groupName) - NavigationLink(destination: LazyClosureView(ContactPicker(selectedContacts: $selectedContacts)), label: { - Text("Group Members") + NavigationLink(destination: LazyClosureView(ContactPicker(account: self.selectedAccount!, selectedContacts: $selectedContacts)), label: { + Text("Change Group Members") }) + Button(action: { + showLoadingOverlay(overlay, headline: NSLocalizedString("Creating Group", comment: "")) + let roomJid = self.selectedAccount!.mucProcessor.createGroup(nil) + if(roomJid == nil) { + let groupContact = MLContact.createContact(fromJid: roomJid!, andAccountNo: self.selectedAccount!.accountNo) + hideLoadingOverlay(overlay) + self.delegate.dismissWithoutAnimation() + if let activeChats = self.appDelegate.obj.activeChats { + activeChats.presentChat(with:groupContact) + } + } else { + self.selectedAccount!.mucProcessor.addUIHandler({data in + let success : Bool = (data as! NSDictionary)["success"] as! Bool; + if(success) { + self.selectedAccount!.mucProcessor.changeName(ofMuc: roomJid!, to: self.groupName) + for user in self.selectedContacts { + self.selectedAccount!.mucProcessor.setAffiliation("member", ofUser: user.contactJid, inMuc: roomJid!) + self.selectedAccount!.mucProcessor.inviteUser(user.contactJid, inMuc: roomJid!) + } + let groupContact = MLContact.createContact(fromJid: roomJid!, andAccountNo: self.selectedAccount!.accountNo) + hideLoadingOverlay(overlay) + self.delegate.dismissWithoutAnimation() + if let activeChats = self.appDelegate.obj.activeChats { + activeChats.presentChat(with:groupContact) + } + } else { + hideLoadingOverlay(overlay) + errorAlert(title: Text("Error creating group!")) + } + }, forMuc: roomJid!) + } + }, label: { + Text("Create new group") + }) } if(self.selectedContacts.count > 0) { Section(header: Text("Selected Group Members")) { @@ -96,22 +110,12 @@ struct CreateGroupMenu: View { self.selectedContacts.remove(at: indexSet.first!) }) } - Section { - Button(action: { - - }, label: { - Text("Create new group") - }) - } } } } .alert(isPresented: $showAlert) { Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton:.default(Text("Close"), action: { showAlert = false - if self.success == true { - // TODO dismissWithNewGroup - } })) } .addLoadingOverlay(overlay) @@ -123,7 +127,6 @@ struct CreateGroupMenu: View { struct CreateGroupMenu_Previews: PreviewProvider { static var delegate = SheetDismisserProtocol() static var previews: some View { - CreateGroupMenu(delegate: delegate, dismissWithNewGroup: { c in - }) + CreateGroupMenu(delegate: delegate) } } diff --git a/Monal/Classes/DataLayer.h b/Monal/Classes/DataLayer.h index 9b879af522..3895f18b6f 100644 --- a/Monal/Classes/DataLayer.h +++ b/Monal/Classes/DataLayer.h @@ -60,8 +60,9 @@ extern NSString* const kMessageTypeFiletransfer; -(NSMutableArray*) searchContactsWithString:(NSString*) search; --(NSMutableArray*) contactList; --(NSMutableArray*) contactListWithJid:(NSString*) jid; +-(NSArray*) contactList; +-(NSArray*) contactListWithJid:(NSString*) jid; +-(NSArray*) possibleGroupMembersForAccount:(NSNumber*) accountNo; -(NSArray*) resourcesForContact:(MLContact* _Nonnull)contact ; -(MLContactSoftwareVersionInfo* _Nullable) getSoftwareVersionInfoForContact:(NSString*)contact resource:(NSString*)resource andAccount:(NSNumber*)account; -(void) setSoftwareVersionInfoForContact:(NSString*)contact diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index c8d9851187..3a42080461 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -501,12 +501,24 @@ -(NSDictionary* _Nullable) contactDictionaryForUsername:(NSString*) username for }]; } --(NSMutableArray*) contactList +-(NSArray*) contactList { return [self contactListWithJid:@""]; } --(NSMutableArray*) contactListWithJid:(NSString*) jid +-(NSArray*) possibleGroupMembersForAccount:(NSNumber*) accountNo +{ + return [self.db idReadTransaction:^{ + //list all contacts and group chats + NSString* query = @"SELECT buddy_name, account_id, IFNULL(IFNULL(NULLIF(nick_name, ''), NULLIF(full_name, '')), buddy_name) FROM buddylist WHERE account_id=? AND muc=0"; + NSMutableArray* toReturn = [NSMutableArray new]; + for(NSDictionary* dic in [self.db executeReader:query andArguments:@[accountNo]]) + [toReturn addObject:[MLContact createContactFromJid:dic[@"buddy_name"] andAccountNo:dic[@"account_id"]]]; + return toReturn; + }]; +} + +-(NSArray*) contactListWithJid:(NSString*) jid { return [self.db idReadTransaction:^{ //list all contacts and group chats diff --git a/Monal/Classes/MLMucProcessor.h b/Monal/Classes/MLMucProcessor.h index 8a43f46389..f9283990f6 100644 --- a/Monal/Classes/MLMucProcessor.h +++ b/Monal/Classes/MLMucProcessor.h @@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN -(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject; -(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room; -(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSString*) roomJid; +-(void) inviteUser:(NSString*) jid inMuc:(NSString*) roomJid; -(void) pingAllMucs; -(void) ping:(NSString*) roomJid; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 0f7e829396..4edf30c995 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -972,6 +972,26 @@ -(void) ping:(NSString*) roomJid withLastPing:(NSDate* _Nullable) lastPing }]; } +-(void) inviteUser:(NSString*) jid inMuc:(NSString*) roomJid +{ + DDLogInfo(@"Inviting user '%@' to '%@' directly & indirectly", jid, roomJid); + XMPPMessage* indirectInviteMsg = [[XMPPMessage alloc] initWithType:kMessageNormalType to:roomJid]; + [indirectInviteMsg addChildNode:[[MLXMLNode alloc] initWithElement:@"x" andNamespace:@"http://jabber.org/protocol/muc#user" withAttributes:@{} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"invite" withAttributes:@{ + @"to": jid + } andChildren:@[] andData:nil] + ] andData:nil]]; + + [_account send:indirectInviteMsg]; + + XMPPMessage* directInviteMsg = [[XMPPMessage alloc] initWithType:kMessageNormalType to:jid]; + [directInviteMsg addChildNode:[[MLXMLNode alloc] initWithElement:@"x" andNamespace:@"jabber:x:conference" withAttributes:@{ + @"jid": roomJid + } andChildren:@[] andData:nil]]; + + [_account send:directInviteMsg]; +} + -(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSString*) roomJid { DDLogInfo(@"Changing affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 88fca399eb..1a30ad6c0c 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -13,10 +13,9 @@ import OrderedCollections struct MemberList: View { @Environment(\.editMode) private var editMode - private let memberList: [ObservableKVOWrapper] - private let groupName: String + @State private var memberList: [ObservableKVOWrapper] + @ObservedObject var group: ObservableKVOWrapper private let account: xmpp? - private let isAlpha: Bool @State private var openAccountSelection : Bool = false @State private var contactsToAdd : OrderedSet = [] @@ -31,9 +30,9 @@ struct MemberList: View { var body: some View { // This is the invisible NavigationLink hack again... - NavigationLink(destination:LazyClosureView(ContactPicker(selectedContacts: $contactsToAdd)), isActive: $openAccountSelection){}.hidden().disabled(true) // navigation happens as soon as our button sets navigateToQRCodeView to true... + NavigationLink(destination:LazyClosureView(ContactPicker(account: self.account!, selectedContacts: $contactsToAdd)), isActive: $openAccountSelection){}.hidden().disabled(true) // navigation happens as soon as our button sets navigateToQRCodeView to true... List { - Section(header: Text(self.groupName)) { + Section(header: Text(self.group.obj.contactDisplayName)) { ForEach(self.memberList, id: \.self.obj) { contact in if contact.obj.contactJid != self.account?.connectionProperties.identity.jid { @@ -56,18 +55,19 @@ struct MemberList: View { } } .onDelete(perform: { memberIdx in - // TODO maybe alert before deletion - if(memberIdx.count == 1) { - self.setAndShowAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) - } + let member = self.memberList[memberIdx.first!] + self.account!.mucProcessor.setAffiliation("none", ofUser: member.contactJid, inMuc: self.group.obj.contactJid) + + self.setAndShowAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) + self.memberList.remove(at: memberIdx.first!) }) - .deleteDisabled(self.isAlpha) }.alert(isPresented: $showAlert, content: { Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton: .default(alertPrompt.dismissLabel)) }) } .toolbar { - if(isAlpha && editMode?.wrappedValue.isEditing == true) { +#if IS_ALPA + if(editMode?.wrappedValue.isEditing == true) { Button(action: { openAccountSelection = true }, label: { @@ -75,26 +75,16 @@ struct MemberList: View { .foregroundColor(.blue) }) } +#endif EditButton() } .navigationBarTitle("Group Members", displayMode: .inline) } init(mucContact: ObservableKVOWrapper?) { - if let mucContact = mucContact { - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact.obj.accountId)! as xmpp - self.groupName = mucContact.contactDisplayName - self.memberList = getContactList(viewContact: mucContact) - } else { - self.account = nil - self.groupName = "Invalid Group" - self.memberList = [] - } -#if IS_ALPA - self.isAlpha = true -#else - self.isAlpha = false -#endif + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact!.obj.accountId)! as xmpp + self.group = mucContact!; + _memberList = State(wrappedValue: getContactList(viewContact: mucContact)) } } diff --git a/Monal/Classes/SwiftuiHelpers.swift b/Monal/Classes/SwiftuiHelpers.swift index bdf9eaa922..a1a1e7e434 100644 --- a/Monal/Classes/SwiftuiHelpers.swift +++ b/Monal/Classes/SwiftuiHelpers.swift @@ -473,9 +473,7 @@ class SwiftuiInterface : NSObject { case "ContactRequests": host.rootView = AnyView(AddTopLevelNavigation(withDelegate: delegate, to: ContactRequestsMenu(delegate: delegate))) case "CreateGroup": - host.rootView = AnyView(AddTopLevelNavigation(withDelegate: delegate, to: CreateGroupMenu(delegate: delegate, dismissWithNewGroup: { contact in - // FIXME - }))) + host.rootView = AnyView(AddTopLevelNavigation(withDelegate: delegate, to: CreateGroupMenu(delegate: delegate))) case "ChatPlaceholder": host.rootView = AnyView(ChatPlaceholder()) default: diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index 269ec37e6b..df4275fcdb 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -170,6 +170,7 @@ C1049189261301530054AC9E /* MonalXMPPUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1049188261301530054AC9E /* MonalXMPPUnitTests.swift */; }; C104918B261301530054AC9E /* monalxmpp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26CC579223A0867400ABB92A /* monalxmpp.framework */; }; C1049199261301710054AC9E /* MLCryptoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1049198261301710054AC9E /* MLCryptoTest.swift */; }; + C114D13D2B15B903000FB99F /* ContactEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C114D13C2B15B903000FB99F /* ContactEntry.swift */; }; C117F7E12B086390001F2BC6 /* CreateGroupMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88BB76295BB6DC00FB30BA /* CreateGroupMenu.swift */; }; C117F7E22B0863B3001F2BC6 /* ContactPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D631822294BAB1D00026BE7 /* ContactPicker.swift */; }; C12436142434AB5D00B8F074 /* MLAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = C12436132434AB5D00B8F074 /* MLAttributedLabel.m */; }; @@ -614,6 +615,7 @@ C1049188261301530054AC9E /* MonalXMPPUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonalXMPPUnitTests.swift; sourceTree = ""; }; C104918A261301530054AC9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C1049198261301710054AC9E /* MLCryptoTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLCryptoTest.swift; sourceTree = ""; }; + C114D13C2B15B903000FB99F /* ContactEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactEntry.swift; sourceTree = ""; }; C12436122434AB5D00B8F074 /* MLAttributedLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLAttributedLabel.h; sourceTree = ""; }; C12436132434AB5D00B8F074 /* MLAttributedLabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLAttributedLabel.m; sourceTree = ""; }; C132EA9426C92DD900BB9A67 /* pa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pa; path = external/pa.lproj/Main.strings; sourceTree = ""; }; @@ -1061,6 +1063,7 @@ 3D27D957290B0BC80014748B /* ContactRequestsMenu.swift */, 846DF27B2937FAA600AAB9C0 /* ChatPlaceholder.swift */, 841898AB2957DBAC00FEC77D /* RichAlert.swift */, + C114D13C2B15B903000FB99F /* ContactEntry.swift */, ); name = "View Controllers"; path = Classes; @@ -2021,6 +2024,7 @@ 848717F3295ED64600B8D288 /* MLCall.m in Sources */, 26D89F061A890672009B147C /* MLSwitchCell.m in Sources */, C13EBB8E24DC685C008AADDA /* MLPrivacySettingsViewController.m in Sources */, + C114D13D2B15B903000FB99F /* ContactEntry.swift in Sources */, 841B6F1A297B18720074F9B7 /* AccountPicker.swift in Sources */, 3D65B791272350F0005A30F4 /* SwiftuiHelpers.swift in Sources */, C1A80DA424D9552400B99E01 /* MLChatViewHelper.m in Sources */, diff --git a/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Alpha.xcscheme b/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Alpha.xcscheme index cc98bb9d87..2ba3b11376 100644 --- a/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Alpha.xcscheme +++ b/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Alpha.xcscheme @@ -34,7 +34,6 @@ buildConfiguration = "Alpha" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - enableAddressSanitizer = "NO" enableASanStackUseAfterReturn = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" diff --git a/Monal/Podfile.lock b/Monal/Podfile.lock index c23d9a1fd3..3237ab2b8c 100644 --- a/Monal/Podfile.lock +++ b/Monal/Podfile.lock @@ -125,4 +125,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: f415f317e34fd11a687374f84ee8dfb14ebb29a6 -COCOAPODS: 1.15.0 +COCOAPODS: 1.15.2 diff --git a/Monal/shareSheet-iOS/ShareViewController.m b/Monal/shareSheet-iOS/ShareViewController.m index e30d38febe..241b60ecf0 100644 --- a/Monal/shareSheet-iOS/ShareViewController.m +++ b/Monal/shareSheet-iOS/ShareViewController.m @@ -68,9 +68,7 @@ - (void) presentationAnimationDidFinish // list all contacts, not only active chats // that will clutter the list of selectable contacts, but you can always use sirikit interactions // to get the recently used contacts listed - NSMutableArray* recipients = [[DataLayer sharedInstance] contactList]; - - self.recipients = recipients; + self.recipients = [[DataLayer sharedInstance] contactList]; self.accounts = [[DataLayer sharedInstance] enabledAccountList]; if(self.intentContact != nil) From 6dd77f87f366ad6a18cdfceea3b6a1f81ccec3f5 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 17:37:05 +0100 Subject: [PATCH 016/115] Fix muc status code handling and formatting in error notifications --- Monal/Classes/HelperTools.m | 6 ++++-- Monal/Classes/MLMucProcessor.m | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 076b012fcb..a966ec2172 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -358,11 +358,13 @@ +(NSString*) extractXMPPError:(XMPPStanza*) stanza withDescription:(NSString*) d { if(description == nil || [description isEqualToString:@""]) description = @"XMPP Error"; + NSMutableString* message = [description mutableCopy]; NSString* errorReason = [stanza findFirst:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}!text$"]; + if(errorReason && ![errorReason isEqualToString:@""]) + [message appendString:[NSString stringWithFormat:@": %@", errorReason]]; NSString* errorText = [stanza findFirst:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}text#"]; - NSString* message = [NSString stringWithFormat:@"%@: %@", description, errorReason]; if(errorText && ![errorText isEqualToString:@""]) - message = [NSString stringWithFormat:@"%@: %@ (%@)", description, errorReason, errorText]; + [message appendString:[NSString stringWithFormat:@" (%@)", errorText]]; return message; } diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 4edf30c995..daacd02a9d 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -603,6 +603,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node [self removeRoomFromJoining:node.fromUser]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got banned from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } + break; } //kicked from room case 307: @@ -614,17 +615,23 @@ -(void) handleStatusCodes:(XMPPStanza*) node [self removeRoomFromJoining:node.fromUser]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got kicked from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } + break; } //removed because of affiliation change --> reenter room case 321: { - DDLogDebug(@"user '%@' got affiliation changed for room %@", node.fromResource, node.fromUser); - if([nick isEqualToString:node.fromResource]) + //only handle this and rejoin, if we did not get removed from a members-only room + if(![presenceCodes containsObject:@322]) { - DDLogDebug(@"got affiliation change for room %@", node.fromUser); - [self removeRoomFromJoining:node.fromUser]; - [self sendDiscoQueryFor:node.fromUser withJoin:YES andBookmarksUpdate:YES]; + DDLogDebug(@"user '%@' got affiliation changed for room %@", node.fromResource, node.fromUser); + if([nick isEqualToString:node.fromResource]) + { + DDLogDebug(@"got affiliation change for room %@", node.fromUser); + [self removeRoomFromJoining:node.fromUser]; + [self sendDiscoQueryFor:node.fromUser withJoin:YES andBookmarksUpdate:YES]; + } } + break; } //removed because room is now members only (an we are not a member) case 322: @@ -637,6 +644,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because muc is now members-only: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; } + break; } //removed because of system shutdown case 332: @@ -648,6 +656,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node [self removeRoomFromJoining:node.fromUser]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because of system shutdown: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } + break; } default: DDLogWarn(@"Got unhandled muc status code in presence from %@: %@", node.from, code); From 3ab64953fd33d4376dcf722558903b84675d9b1e Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:40:30 +0100 Subject: [PATCH 017/115] bump crates --- rust/Cargo.lock | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 81e8c01cbf..93a828e8e6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.4.2" @@ -127,22 +121,13 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "rustix" version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -176,7 +161,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.49", ] [[package]] @@ -237,9 +222,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", @@ -248,13 +233,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys", ] From ffb293b2c33363cbf925fb2bb9629fb5297fba32 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:40:45 +0100 Subject: [PATCH 018/115] Bump pods --- Monal/Podfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Monal/Podfile.lock b/Monal/Podfile.lock index 3237ab2b8c..37e6913219 100644 --- a/Monal/Podfile.lock +++ b/Monal/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - ASN1Decoder (1.9.0) + - ASN1Decoder (1.10.0) - DZNEmptyDataSet (1.8.1) - FLAnimatedImage (1.0.17) - KSCrash/Core (1.16.2): @@ -42,9 +42,9 @@ PODS: - MarqueeLabel (~> 4.3.0) - SnapKit (~> 5.6.0) - SAMKeychain (1.5.3) - - SDWebImage (5.18.10): - - SDWebImage/Core (= 5.18.10) - - SDWebImage/Core (5.18.10) + - SDWebImage (5.18.11): + - SDWebImage/Core (= 5.18.11) + - SDWebImage/Core (5.18.11) - SignalProtocolC (2.3.3) - SignalProtocolObjC (1.1.1): - SignalProtocolC (~> 2.3.3) @@ -53,7 +53,7 @@ PODS: - sqlite3/perf-threadsafe (3.45.1): - sqlite3/common - TOCropViewController (2.6.1) - - WebRTC-lib (120.0.0) + - WebRTC-lib (121.0.0) DEPENDENCIES: - ASN1Decoder @@ -107,7 +107,7 @@ CHECKOUT OPTIONS: :git: https://github.com/monal-im/SignalProtocol-ObjC.git SPEC CHECKSUMS: - ASN1Decoder: 4f4bbcaf1d1b8be56daa3280e82863a607f5bda9 + ASN1Decoder: 91cb1d781b5a178ea7375b2f1519e2bdaaa4c427 DZNEmptyDataSet: 9525833b9e68ac21c30253e1d3d7076cc828eaa7 FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b KSCrash: 469b9f982b97309acb719baaecbf9182f62ddf85 @@ -115,13 +115,13 @@ SPEC CHECKSUMS: MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406 NotificationBannerSwift: dce54ded532b26e30cd8e7f4d80e124a0f2ba7d1 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SDWebImage: fc8f2d48bbfd72ef39d70e981bd24a3f3be53fec + SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457 SignalProtocolC: 8092866e45b663a6bc3e45a8d13bad2571dbf236 SignalProtocolObjC: 1beb46b1d35733e7ab96a919f88bac20ec771c73 SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 sqlite3: 73b7fc691fdc43277614250e04d183740cb15078 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 - WebRTC-lib: bd0d4b76aab1492415216715bd02f4329a33cd10 + WebRTC-lib: cedcd0017ad839220b2dbce943e9c43c24dea036 PODFILE CHECKSUM: f415f317e34fd11a687374f84ee8dfb14ebb29a6 From 9cdd93fd91949e6b24c2373430a9513f194ee75f Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 17:58:19 +0100 Subject: [PATCH 019/115] Fix more muc status code handling bugs on self presences --- Monal/Classes/MLMucProcessor.m | 179 ++++++++++++++++----------------- 1 file changed, 89 insertions(+), 90 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index daacd02a9d..a628e8e533 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -574,25 +574,6 @@ -(void) handleStatusCodes:(XMPPStanza*) node } break; } - //this is a self-presence (marking the end of the presence flood if we are in joining state) - case 110: - { - //check if we have joined already (we handle only non-joining self-presences here) - //joining self-presences are handled below - if(![self isJoining:node.fromUser]) - { - //muc got destroyed - if([node check:@"//{http://jabber.org/protocol/muc#user}x/destroy"]) - { - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Group/Channel got destroyed: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; - [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; - } - else - ; //ignore other non-joining self-presences for now - break; - } - break; - } //banned from room case 301: { @@ -662,88 +643,106 @@ -(void) handleStatusCodes:(XMPPStanza*) node DDLogWarn(@"Got unhandled muc status code in presence from %@: %@", node.from, code); } - //this is a self-presence marking the end of the presence flood, handle this code last because it resets _joining - if([presenceCodes containsObject:@110] && [self isJoining:node.fromUser]) + //this is a self-presence (marking the end of the presence flood if we are in joining state) + //handle this code last because it may reset _joining + if([presenceCodes containsObject:@110]) { - DDLogInfo(@"Successfully joined muc %@...", node.fromUser); - - //we are joined now, remove from joining list - [self removeRoomFromJoining:node.fromUser]; - - //we joined successfully --> add muc to our favorites (this will use the already up to date nick from buddylist db table) - //and update bookmarks if this was the first time we joined this muc - [[DataLayer sharedInstance] addMucFavorite:node.fromUser forAccountId:_account.accountNo andMucNick:nil]; - @synchronized(_stateLockObject) { - DDLogVerbose(@"_firstJoin set: %@\n_noUpdateBookmarks set: %@", _firstJoin, _noUpdateBookmarks); - //only update bookmarks on first join AND if not requested otherwise (batch join etc.) - if([_firstJoin containsObject:node.fromUser] && ![_noUpdateBookmarks containsObject:node.fromUser]) - [self updateBookmarks]; - [_firstJoin removeObject:node.fromUser]; - [_noUpdateBookmarks removeObject:node.fromUser]; - } - - [self logMembersOfMuc:node.fromUser]; - - //load members/admins/owners list (this has to be done *after* joining the muc to not get auth errors) - DDLogInfo(@"Querying member/admin/owner lists for muc %@...", node.fromUser); - for(NSString* type in @[@"member", @"admin", @"owner"]) - { - XMPPIQ* discoInfo = [[XMPPIQ alloc] initWithType:kiqGetType to:node.fromUser]; - [discoInfo setMucListQueryFor:type]; - [_account sendIq:discoInfo withHandler:$newHandler(self, handleMembersList, $ID(type))]; - } - - monal_id_block_t uiHandler = [self getUIHandlerForMuc:node.fromUser]; - if(uiHandler) + //check if we have joined already (we handle only non-joining self-presences here) + //joining self-presences are handled below + if(![self isJoining:node.fromUser]) { - //remove handler (it will only be called once) - [self removeUIHandlerForMuc:node.fromUser]; + DDLogInfo(@"Got non-joining muc presence for %@...", node.fromUser); - DDLogInfo(@"Calling UI handler for muc %@...", node.fromUser); - dispatch_async(dispatch_get_main_queue(), ^{ - uiHandler(@{ - @"success": @YES, - @"muc": node.fromUser, - @"account": self->_account - }); - }); - } - - //MAYBE TODO: send out notification indicating we joined that room - - //query muc-mam for new messages - BOOL supportsMam = NO; - @synchronized(_stateLockObject) { - if(_roomFeatures[node.fromUser] && [_roomFeatures[node.fromUser] containsObject:@"urn:xmpp:mam:2"]) - supportsMam = YES; + //handle muc destroys, but ignore other non-joining self-presences for now + //(normally these have an additional status code that was already handled in the switch statement above + if([node check:@"//{http://jabber.org/protocol/muc#user}x/destroy"]) + { + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Group/Channel got destroyed: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; + } } - if(supportsMam) + else { - DDLogInfo(@"Muc %@ supports mam:2...", node.fromUser); + DDLogInfo(@"Successfully joined muc %@...", node.fromUser); + + //we are joined now, remove from joining list + [self removeRoomFromJoining:node.fromUser]; + + //we joined successfully --> add muc to our favorites (this will use the already up to date nick from buddylist db table) + //and update bookmarks if this was the first time we joined this muc + [[DataLayer sharedInstance] addMucFavorite:node.fromUser forAccountId:_account.accountNo andMucNick:nil]; + @synchronized(_stateLockObject) { + DDLogVerbose(@"_firstJoin set: %@\n_noUpdateBookmarks set: %@", _firstJoin, _noUpdateBookmarks); + //only update bookmarks on first join AND if not requested otherwise (batch join etc.) + if([_firstJoin containsObject:node.fromUser] && ![_noUpdateBookmarks containsObject:node.fromUser]) + [self updateBookmarks]; + [_firstJoin removeObject:node.fromUser]; + [_noUpdateBookmarks removeObject:node.fromUser]; + } + + [self logMembersOfMuc:node.fromUser]; - //query mam since last received stanza ID because we could not resume the smacks session - //(we would not have landed here if we were able to resume the smacks session) - //this will do a catchup of everything we might have missed since our last connection - //we possibly receive sent messages, too (this will update the stanzaid in database and gets deduplicate by messageid, - //which is guaranteed to be unique (because monal uses uuids for outgoing messages) - NSString* lastStanzaId = [[DataLayer sharedInstance] lastStanzaIdForMuc:node.fromUser andAccount:_account.accountNo]; - [_account delayIncomingMessageStanzasForArchiveJid:node.fromUser]; - XMPPIQ* mamQuery = [[XMPPIQ alloc] initWithType:kiqSetType to:node.fromUser]; - if(lastStanzaId) + //load members/admins/owners list (this has to be done *after* joining the muc to not get auth errors) + DDLogInfo(@"Querying member/admin/owner lists for muc %@...", node.fromUser); + for(NSString* type in @[@"member", @"admin", @"owner"]) { - DDLogInfo(@"Querying muc mam:2 archive after stanzaid '%@' for catchup", lastStanzaId); - [mamQuery setMAMQueryAfter:lastStanzaId]; - [_account sendIq:mamQuery withHandler:$newHandler(self, handleCatchup, $BOOL(secondTry, NO))]; + XMPPIQ* discoInfo = [[XMPPIQ alloc] initWithType:kiqGetType to:node.fromUser]; + [discoInfo setMucListQueryFor:type]; + [_account sendIq:discoInfo withHandler:$newHandler(self, handleMembersList, $ID(type))]; } - else + + monal_id_block_t uiHandler = [self getUIHandlerForMuc:node.fromUser]; + if(uiHandler) { - DDLogInfo(@"Querying muc mam:2 archive for latest stanzaid to prime database"); - [mamQuery setMAMQueryForLatestId]; - [_account sendIq:mamQuery withHandler:$newHandler(self, handleMamResponseWithLatestId)]; + //remove handler (it will only be called once) + [self removeUIHandlerForMuc:node.fromUser]; + + DDLogInfo(@"Calling UI handler for muc %@...", node.fromUser); + dispatch_async(dispatch_get_main_queue(), ^{ + uiHandler(@{ + @"success": @YES, + @"muc": node.fromUser, + @"account": self->_account + }); + }); } + + //MAYBE TODO: send out notification indicating we joined that room + + //query muc-mam for new messages + BOOL supportsMam = NO; + @synchronized(_stateLockObject) { + if(_roomFeatures[node.fromUser] && [_roomFeatures[node.fromUser] containsObject:@"urn:xmpp:mam:2"]) + supportsMam = YES; + } + if(supportsMam) + { + DDLogInfo(@"Muc %@ supports mam:2...", node.fromUser); + + //query mam since last received stanza ID because we could not resume the smacks session + //(we would not have landed here if we were able to resume the smacks session) + //this will do a catchup of everything we might have missed since our last connection + //we possibly receive sent messages, too (this will update the stanzaid in database and gets deduplicate by messageid, + //which is guaranteed to be unique (because monal uses uuids for outgoing messages) + NSString* lastStanzaId = [[DataLayer sharedInstance] lastStanzaIdForMuc:node.fromUser andAccount:_account.accountNo]; + [_account delayIncomingMessageStanzasForArchiveJid:node.fromUser]; + XMPPIQ* mamQuery = [[XMPPIQ alloc] initWithType:kiqSetType to:node.fromUser]; + if(lastStanzaId) + { + DDLogInfo(@"Querying muc mam:2 archive after stanzaid '%@' for catchup", lastStanzaId); + [mamQuery setMAMQueryAfter:lastStanzaId]; + [_account sendIq:mamQuery withHandler:$newHandler(self, handleCatchup, $BOOL(secondTry, NO))]; + } + else + { + DDLogInfo(@"Querying muc mam:2 archive for latest stanzaid to prime database"); + [mamQuery setMAMQueryForLatestId]; + [_account sendIq:mamQuery withHandler:$newHandler(self, handleMamResponseWithLatestId)]; + } + } + + //we don't need to force saving of our new state because once this incoming presence gets counted by smacks the whole state will be saved } - - //we don't need to force saving of our new state because once this incoming presence gets counted by smacks the whole state will be saved } } //handle message stanzas From aa99e6adb4c8fc46160b098c5935e77777e1944e Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 17 Feb 2024 19:11:33 +0100 Subject: [PATCH 020/115] Do not show own jid in group member list selection --- Monal/Classes/DataLayer.m | 2 +- Monal/Classes/MemberList.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index 3a42080461..d5a83750a1 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -510,7 +510,7 @@ -(NSDictionary* _Nullable) contactDictionaryForUsername:(NSString*) username for { return [self.db idReadTransaction:^{ //list all contacts and group chats - NSString* query = @"SELECT buddy_name, account_id, IFNULL(IFNULL(NULLIF(nick_name, ''), NULLIF(full_name, '')), buddy_name) FROM buddylist WHERE account_id=? AND muc=0"; + NSString* query = @"SELECT B.buddy_name, B.account_id, IFNULL(IFNULL(NULLIF(B.nick_name, ''), NULLIF(B.full_name, '')), B.buddy_name) FROM buddylist as B INNER JOIN account AS A ON A.account_id=B.account_id WHERE B.account_id=? AND B.muc=0 AND B.buddy_name != (A.username || '@' || A.domain)"; NSMutableArray* toReturn = [NSMutableArray new]; for(NSDictionary* dic in [self.db executeReader:query andArguments:@[accountNo]]) [toReturn addObject:[MLContact createContactFromJid:dic[@"buddy_name"] andAccountNo:dic[@"account_id"]]]; diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 1a30ad6c0c..844b77ad53 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -66,7 +66,7 @@ struct MemberList: View { }) } .toolbar { -#if IS_ALPA +#if IS_ALPHA if(editMode?.wrappedValue.isEditing == true) { Button(action: { openAccountSelection = true From f02b4dd3e0ee4d23ec1fb590cd9b8bff97412240 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 19:28:35 +0100 Subject: [PATCH 021/115] Rename webrtc stream and track ids --- Monal/Classes/WebRTCClient.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/WebRTCClient.swift b/Monal/Classes/WebRTCClient.swift index ab161c98a4..481ed73ad7 100644 --- a/Monal/Classes/WebRTCClient.swift +++ b/Monal/Classes/WebRTCClient.swift @@ -32,7 +32,7 @@ final class WebRTCClient: NSObject { @objc public let peerConnection: RTCPeerConnection private let rtcAudioSession = RTCAudioSession.sharedInstance() private let audioQueue = DispatchQueue(label: "audio") - private let streamId = "stream" + private let streamId = "-" private var mediaConstrains: [String:String] = [:] private var videoCapturer: RTCVideoCapturer? private var localVideoTrack: RTCVideoTrack? @@ -250,7 +250,7 @@ final class WebRTCClient: NSObject { private func createAudioTrack() -> RTCAudioTrack { let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) let audioSource = WebRTCClient.factory.audioSource(with: audioConstrains) - let audioTrack = WebRTCClient.factory.audioTrack(with: audioSource, trackId: "audio0") + let audioTrack = WebRTCClient.factory.audioTrack(with: audioSource, trackId: "audio-"+UUID().uuidString) return audioTrack } @@ -274,7 +274,7 @@ final class WebRTCClient: NSObject { method_setImplementation(swizzledMethod!, replacementImplementation) } - let videoTrack = WebRTCClient.factory.videoTrack(with: videoSource, trackId: "video0") + let videoTrack = WebRTCClient.factory.videoTrack(with: videoSource, trackId: "video-"+UUID().uuidString) return videoTrack } @@ -362,6 +362,7 @@ extension WebRTCClient { setTrackEnabled(RTCVideoTrack.self, isEnabled: isEnabled) } } + // MARK:- Audio control extension WebRTCClient { @objc From cce47eb2d745860b7077b726b774a9a7861b0608 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 19:40:41 +0100 Subject: [PATCH 022/115] Make $call explicitly check for nil handler --- Monal/Classes/MLHandler.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLHandler.h b/Monal/Classes/MLHandler.h index 1cf4b8c911..74aff082bf 100644 --- a/Monal/Classes/MLHandler.h +++ b/Monal/Classes/MLHandler.h @@ -131,8 +131,8 @@ MLHandler* h = $newHandlerWithInvalidation(ClassName, myHandlerName, myInvalidat #define $$ } //call handler/invalidation -#define $call(handler, ...) [handler callWithArguments:@{ __VA_ARGS__ }] -#define $invalidate(handler, ...) [handler invalidateWithArguments:@{ __VA_ARGS__ }] +#define $call(handler, ...) do { if(handler != nil) { [handler callWithArguments:@{ __VA_ARGS__ }]; } } while(0) +#define $invalidate(handler, ...) do { if(handler != nil) { [handler invalidateWithArguments:@{ __VA_ARGS__ }]; } } while(0) //internal stuff //$_*() and $$*() will add parentheses around its result to make sure all inner commas like those probably exposed by an inner STRIP_PARENTHESES() call get not From 99a27f8f2f124937cc9e556c33853a952e7e45d8 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 20:06:30 +0100 Subject: [PATCH 023/115] Fix configuring muc after creating it (room name etc.) --- Monal/Classes/MLMucProcessor.m | 46 +++++++++++----------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index a628e8e533..b04004578e 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -391,20 +391,15 @@ -(void) handleMembersListUpdate:(XMPPStanza*) node DDLogInfo(@"Ignoring handleMembersListUpdate for %@, MUC not in buddylist", node.fromUser); } --(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) mandatoryOptions andOptionalOptions:(NSDictionary*) optionalOptions deletingMucOnError:(BOOL) deleteOnError +-(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) mandatoryOptions andOptionalOptions:(NSDictionary*) optionalOptions deletingMucOnError:(BOOL) deleteOnError andJoiningMucOnSuccess:(BOOL) joinOnSuccess { DDLogInfo(@"Fetching room config form: %@", roomJid); XMPPIQ* configFetchNode = [[XMPPIQ alloc] initWithType:kiqGetType to:roomJid]; [configFetchNode setGetRoomConfig]; - [_account sendIq:configFetchNode withHandler:$newHandlerWithInvalidation(self, handleRoomConfigForm, handleRoomConfigFormInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; + [_account sendIq:configFetchNode withHandler:$newHandlerWithInvalidation(self, handleRoomConfigForm, handleRoomConfigFormInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError), $BOOL(joinOnSuccess))]; } $$instance_handler(handleRoomConfigFormInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) - if(![self isCreating:roomJid]) - { - DDLogError(@"Got room config form invalidation but not creating group, ignoring: %@", roomJid); - return; - } if(deleteOnError) { DDLogError(@"Config form fetch failed, removing muc '%@' from _creating...", roomJid); @@ -416,12 +411,7 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could fetch room config form for '%@': timeout", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; $$ -$$instance_handler(handleRoomConfigForm, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) - if(![self isCreating:iqNode.fromUser]) - { - DDLogError(@"Got room form result but not creating group, ignoring: %@", iqNode); - return; - } +$$instance_handler(handleRoomConfigForm, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError), $$BOOL(joinOnSuccess)) MLAssert([iqNode.fromUser isEqualToString:roomJid], @"Room config form response jid not matching query jid!", (@{ @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], @"roomJid": [NSString stringWithFormat:@"%@", roomJid], @@ -481,15 +471,10 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma dataForm.type = @"submit"; XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType to:roomJid]; [query setRoomConfig:dataForm]; - [_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleRoomConfigResult, handleRoomConfigResultInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError))]; + [_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleRoomConfigResult, handleRoomConfigResultInvalidation, $ID(roomJid), $ID(mandatoryOptions), $ID(optionalOptions), $BOOL(deleteOnError), $BOOL(joinOnSuccess))]; $$ $$instance_handler(handleRoomConfigResultInvalidation, account.mucProcessor, $$ID(xmpp*, account), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) - if(![self isCreating:roomJid]) - { - DDLogError(@"Got room config invalidation but not creating group, ignoring: %@", roomJid); - return; - } if(deleteOnError) { DDLogError(@"Config form submit failed, removing muc '%@' from _creating...", roomJid); @@ -501,12 +486,7 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Could not configure group '%@': timeout", @""), roomJid] forMuc:roomJid withNode:nil andIsSevere:YES]; $$ -$$instance_handler(handleRoomConfigResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError)) - if(![self isCreating:iqNode.fromUser]) - { - DDLogError(@"Got room config result but not creating group, ignoring: %@", iqNode); - return; - } +$$instance_handler(handleRoomConfigResult, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, roomJid), $$ID(NSDictionary*, mandatoryOptions), $$ID(NSDictionary*, optionalOptions), $$BOOL(deleteOnError), $$BOOL(joinOnSuccess)) if([iqNode check:@"/"]) { DDLogError(@"Failed to submit room config form of '%@': %@", roomJid, [iqNode findFirst:@"error"]); @@ -522,10 +502,14 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma @"iqNode.fromUser": [NSString stringWithFormat:@"%@", iqNode.fromUser], @"roomJid": [NSString stringWithFormat:@"%@", roomJid], })); - //group is now properly configured and we are joined, but all the code handling a proper join was not run - //--> join again to make sure everything is sane - [self removeRoomFromCreating:roomJid]; - [self join:roomJid]; + + if(joinOnSuccess) + { + //group is now properly configured and we are joined, but all the code handling a proper join was not run + //--> join again to make sure everything is sane + [self removeRoomFromCreating:roomJid]; + [self join:roomJid]; + } $$ -(void) handleStatusCodes:(XMPPStanza*) node @@ -555,7 +539,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node } //now configure newly created locked room - [self configureMuc:node.fromUser withMandatoryOptions:_mandatoryGroupConfigOptions andOptionalOptions:_optionalGroupConfigOptions deletingMucOnError:YES]; + [self configureMuc:node.fromUser withMandatoryOptions:_mandatoryGroupConfigOptions andOptionalOptions:_optionalGroupConfigOptions deletingMucOnError:YES andJoiningMucOnSuccess:YES]; //stop processing here to not trigger the "successful join" code below //we will trigger this code by a "second" join presence once the room was created and is not locked anymore @@ -1027,7 +1011,7 @@ -(void) changeNameOfMuc:(NSString*) room to:(NSString*) name { [self configureMuc:room withMandatoryOptions:@{ @"muc#roomconfig_roomname": name, - } andOptionalOptions:@{} deletingMucOnError:NO]; + } andOptionalOptions:@{} deletingMucOnError:NO andJoiningMucOnSuccess:NO]; } -(void) changeSubjectOfMuc:(NSString*) room to:(NSString*) subject From e4eb6889a547cc980c42b47c86beccab5c45d999 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 22:26:27 +0100 Subject: [PATCH 024/115] Ignore muc invites if we're not at least subscribedFrom in our roster --- Monal/Classes/MLMucProcessor.m | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index b04004578e..031e8dae31 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -335,7 +335,14 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode //handle mediated invites if([messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { - DDLogInfo(@"Got mediated muc invite from %@ for %@ --> joining...", [messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"], messageNode.fromUser); + MLContact* inviteFrom = [MLContact createContactFromJid:[messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"] andAccountNo:_account.accountNo]; + DDLogInfo(@"Got mediated muc invite from %@ for %@...", inviteFrom, messageNode.fromUser); + if(!inviteFrom.isSubscribedFrom) + { + DDLogWarn(@"Ignoring invite from %@, this jid isn't at least marked as susbscribedFrom in our roster...", inviteFrom); + return YES; //don't process this further + } + DDLogInfo(@"--> joinging %@...", messageNode.fromUser); [self sendDiscoQueryFor:messageNode.fromUser withJoin:YES andBookmarksUpdate:YES]; return YES; //stop processing in MLMessageProcessor } @@ -343,7 +350,14 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode //handle direct invites if([messageNode check:@"{jabber:x:conference}x@jid"] && [[messageNode findFirst:@"{jabber:x:conference}x@jid"] length] > 0) { - DDLogInfo(@"Got direct muc invite from %@ for %@ --> joining...", messageNode.fromUser, [messageNode findFirst:@"{jabber:x:conference}x@jid"]); + MLContact* inviteFrom = [MLContact createContactFromJid:messageNode.fromUser andAccountNo:_account.accountNo]; + DDLogInfo(@"Got direct muc invite from %@ for %@ --> joining...", inviteFrom, [messageNode findFirst:@"{jabber:x:conference}x@jid"]); + if(!inviteFrom.isSubscribedFrom) + { + DDLogWarn(@"Ignoring invite from %@, this jid isn't at least marked as susbscribedFrom in our roster...", inviteFrom); + return YES; //don't process this further + } + DDLogInfo(@"--> joinging %@...", [messageNode findFirst:@"{jabber:x:conference}x@jid"]); [self sendDiscoQueryFor:[messageNode findFirst:@"{jabber:x:conference}x@jid"] withJoin:YES andBookmarksUpdate:YES]; return YES; //stop processing in MLMessageProcessor } From a8ee960ff9080fa023c0b4634ff706a6111b295d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 23:08:12 +0100 Subject: [PATCH 025/115] Ignore unsolicited version iq responses --- Monal/Classes/MLIQProcessor.m | 19 ++++--------------- Monal/Classes/XMPPIQ.h | 2 +- Monal/Classes/XMPPIQ.m | 8 ++------ Monal/Classes/xmpp.m | 20 ++++++++++---------- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index 8507c8a6d1..a8d185fd06 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -161,13 +161,7 @@ +(void) processSetIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account +(void) processResultIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account { - //this is the only iq result that does not need any state - //WARNING: be careful adding other stateless result handlers (those can impose security risks!) - if([iqNode check:@"{jabber:iq:version}query"]) - { - [self iqVersionResult:iqNode forAccount:account]; - return; - } + //WARNING: be careful adding stateless result handlers here (those can impose security risks!) DDLogWarn(@"Got unhandled result IQ: %@", iqNode); [self respondWithErrorTo:iqNode onAccount:account]; @@ -714,22 +708,17 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode } $$ -+(void) iqVersionResult:(XMPPIQ*) iqNode forAccount:(xmpp*) account -{ +$$class_handler(handleVersionResponse, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode)) NSString* iqAppName = [iqNode findFirst:@"{jabber:iq:version}query/name#"]; NSString* iqAppVersion = [iqNode findFirst:@"{jabber:iq:version}query/version#"]; NSString* iqPlatformOS = [iqNode findFirst:@"{jabber:iq:version}query/os#"]; + //server version info is the only case where there will be no resource --> return here if([iqNode.fromUser isEqualToString:account.connectionProperties.identity.domain]) { account.connectionProperties.serverVersion = [[MLContactSoftwareVersionInfo alloc] initWithJid:iqNode.fromUser andRessource:iqNode.fromResource andAppName:iqAppName andAppVersion:iqAppVersion andPlatformOS:iqPlatformOS andLastInteraction:[NSDate date]]; return; } - // TODO Thilo - if(iqNode.fromResource == Nil) - { - return; - } DDLogVerbose(@"Updating software version info for %@", iqNode.from); NSDate* lastInteraction = [[DataLayer sharedInstance] lastInteractionOfJid:iqNode.fromUser andResource:iqNode.fromResource forAccountNo:account.accountNo]; @@ -743,7 +732,7 @@ +(void) iqVersionResult:(XMPPIQ*) iqNode forAccount:(xmpp*) account [[MLNotificationQueue currentQueue] postNotificationName:kMonalXmppUserSoftWareVersionRefresh object:account userInfo:@{@"versionInfo": newSoftwareVersionInfo}]; -} +$$ +(void) respondWithErrorTo:(XMPPIQ*) iqNode onAccount:(xmpp*) account { diff --git a/Monal/Classes/XMPPIQ.h b/Monal/Classes/XMPPIQ.h index 84e6f49f7f..3b7141b0c0 100644 --- a/Monal/Classes/XMPPIQ.h +++ b/Monal/Classes/XMPPIQ.h @@ -87,7 +87,7 @@ FOUNDATION_EXPORT NSString* const kiqErrorType; /** gets Entity SoftWare Version */ --(void) getEntitySoftWareVersionTo:(NSString*) to; +-(void) getEntitySoftwareVersionInfo; /** removes a contact from the roster diff --git a/Monal/Classes/XMPPIQ.m b/Monal/Classes/XMPPIQ.m index 49b0436885..9643747a7a 100644 --- a/Monal/Classes/XMPPIQ.m +++ b/Monal/Classes/XMPPIQ.m @@ -340,13 +340,9 @@ -(void) httpUploadforFile:(NSString *) file ofSize:(NSNumber *) filesize andCont #pragma mark iq get --(void) getEntitySoftWareVersionTo:(NSString*) to +-(void) getEntitySoftwareVersionInfo { - [self setiqTo:to]; - - MLXMLNode* queryNode = [[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:version"]; - - [self addChildNode:queryNode]; + [self addChildNode:[[MLXMLNode alloc] initWithElement:@"query" andNamespace:@"jabber:iq:version"]]; } #pragma mark MUC diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 4ccd098cec..8f23b5ffab 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -3826,9 +3826,9 @@ -(void) queryDisco -(void) queryServerVersion { - XMPPIQ* serverVersion = [[XMPPIQ alloc] initWithType:kiqGetType]; - [serverVersion getEntitySoftWareVersionTo:self.connectionProperties.identity.domain]; - [self send:serverVersion]; + XMPPIQ* serverVersion = [[XMPPIQ alloc] initWithType:kiqGetType to:self.connectionProperties.identity.domain]; + [serverVersion getEntitySoftwareVersionInfo]; + [self sendIq:serverVersion withHandler:$newHandler(MLIQProcessor, handleVersionResponse)]; } -(void) queryExternalServicesOn:(NSString*) jid @@ -4055,15 +4055,15 @@ -(void) updateLocalBlocklistCache:(NSSet*) blockedJids #pragma mark vcard --(void) getEntitySoftWareVersion:(NSString*) user +-(void) getEntitySoftWareVersion:(NSString*) jid { - NSDictionary* jid = [HelperTools splitJid:user]; - MLAssert(jid[@"resource"] != nil, @"getEntitySoftWareVersion needs a full jid!"); - if([[DataLayer sharedInstance] checkCap:@"jabber:iq:version" forUser:jid[@"user"] andResource:jid[@"resource"] onAccountNo:self.accountNo]) + NSDictionary* split = [HelperTools splitJid:jid]; + MLAssert(split[@"resource"] != nil, @"getEntitySoftWareVersion needs a full jid!"); + if([[DataLayer sharedInstance] checkCap:@"jabber:iq:version" forUser:split[@"user"] andResource:split[@"resource"] onAccountNo:self.accountNo]) { - XMPPIQ* iqEntitySoftWareVersion = [[XMPPIQ alloc] initWithType:kiqGetType]; - [iqEntitySoftWareVersion getEntitySoftWareVersionTo:user]; - [self send:iqEntitySoftWareVersion]; + XMPPIQ* iqEntitySoftWareVersion = [[XMPPIQ alloc] initWithType:kiqGetType to:jid]; + [iqEntitySoftWareVersion getEntitySoftwareVersionInfo]; + [self sendIq:iqEntitySoftWareVersion withHandler:$newHandler(MLIQProcessor, handleVersionResponse)]; } } From 6c2fe4eb3f7afedafadbd1850bd5248712d604e7 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Feb 2024 23:26:54 +0100 Subject: [PATCH 026/115] Clean up code --- Monal/Classes/MLMucProcessor.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 031e8dae31..f2cd123967 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -981,20 +981,19 @@ -(void) ping:(NSString*) roomJid withLastPing:(NSDate* _Nullable) lastPing -(void) inviteUser:(NSString*) jid inMuc:(NSString*) roomJid { DDLogInfo(@"Inviting user '%@' to '%@' directly & indirectly", jid, roomJid); + XMPPMessage* indirectInviteMsg = [[XMPPMessage alloc] initWithType:kMessageNormalType to:roomJid]; [indirectInviteMsg addChildNode:[[MLXMLNode alloc] initWithElement:@"x" andNamespace:@"http://jabber.org/protocol/muc#user" withAttributes:@{} andChildren:@[ [[MLXMLNode alloc] initWithElement:@"invite" withAttributes:@{ @"to": jid } andChildren:@[] andData:nil] ] andData:nil]]; - [_account send:indirectInviteMsg]; XMPPMessage* directInviteMsg = [[XMPPMessage alloc] initWithType:kMessageNormalType to:jid]; [directInviteMsg addChildNode:[[MLXMLNode alloc] initWithElement:@"x" andNamespace:@"jabber:x:conference" withAttributes:@{ @"jid": roomJid } andChildren:@[] andData:nil]]; - [_account send:directInviteMsg]; } From be5b96be2a5b96f39615f4d8812663ccd0bb2229 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 18 Feb 2024 02:39:17 +0100 Subject: [PATCH 027/115] Ignore carbon copied / mam retrieved outgoing muc invites --- Monal/Classes/MLMucProcessor.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index f2cd123967..079e9da7a6 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -335,6 +335,10 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode //handle mediated invites if([messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { + //ignore outgoing carbon copies or mam results + if(![messageNode.toUser isEqualToString:_account.connectionProperties.identity.jid]) + return YES; //stop processing in MLMessageProcessor and ignore this invite + MLContact* inviteFrom = [MLContact createContactFromJid:[messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"] andAccountNo:_account.accountNo]; DDLogInfo(@"Got mediated muc invite from %@ for %@...", inviteFrom, messageNode.fromUser); if(!inviteFrom.isSubscribedFrom) @@ -346,10 +350,14 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode [self sendDiscoQueryFor:messageNode.fromUser withJoin:YES andBookmarksUpdate:YES]; return YES; //stop processing in MLMessageProcessor } - + //handle direct invites if([messageNode check:@"{jabber:x:conference}x@jid"] && [[messageNode findFirst:@"{jabber:x:conference}x@jid"] length] > 0) { + //ignore outgoing carbon copies or mam results + if(![messageNode.toUser isEqualToString:_account.connectionProperties.identity.jid]) + return YES; //stop processing in MLMessageProcessor and ignore this invite + MLContact* inviteFrom = [MLContact createContactFromJid:messageNode.fromUser andAccountNo:_account.accountNo]; DDLogInfo(@"Got direct muc invite from %@ for %@ --> joining...", inviteFrom, [messageNode findFirst:@"{jabber:x:conference}x@jid"]); if(!inviteFrom.isSubscribedFrom) From b022cafd195a4a63c4fdfe957d786e14d3a4f1cf Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 18 Feb 2024 03:05:08 +0100 Subject: [PATCH 028/115] Clean up code --- Monal/Classes/MLMessageProcessor.m | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index bcfc4f5905..f4d3159bdf 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -67,7 +67,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if(![messageNode check:@"/@id"]) { DDLogError(@"Ignoring error messages having an empty ID"); - return message; + return nil; } NSString* errorType = [messageNode findFirst:@"error@type"]; @@ -94,7 +94,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag @"errorReason": errorText }]; - return message; + return nil; } //ignore prosody mod_muc_notifications muc push stanzas (they are only needed to trigger an apns push) @@ -104,13 +104,13 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag NSString* roomJid = [messageNode findFirst:@"{http://quobis.com/xmpp/muc#push}notification@jid"]; if([[[DataLayer sharedInstance] listMucsForAccount:account.accountNo] containsObject:roomJid]) [account.mucProcessor ping:roomJid]; - return message; + return nil; } if([messageNode check:@"//{http://jabber.org/protocol/pubsub#event}event"]) { [account.pubsub handleHeadlineMessage:messageNode]; - return message; + return nil; } if(![messageNode check:@"/"] && [[messageNode from] isEqualToString:account.connectionProperties.identity.fullJid] && [[messageNode toUser] isEqualToString:account.connectionProperties.identity.jid]) { @@ -123,7 +123,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if([messageNode check:@"{urn:xmpp:jingle-message:0}*"] && ![HelperTools shouldProvideVoip]) { DDLogWarn(@"China locale detected, ignoring incoming JMI message!"); - return message; + return nil; } else if([messageNode check:@"{urn:xmpp:jingle-message:0}*"]) { @@ -138,7 +138,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag { //TODO: record this call in history db even if it was outgoing from another device on our account DDLogWarn(@"Ignoring incoming JMI propose coming from another device on our account"); - return message; + return nil; } //only handle jmi stanzas exchanged with contacts allowed to see us and ignore all others @@ -147,7 +147,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if(!jmiContact.isSubscribedFrom) { DDLogWarn(@"Ignoring incoming JMI propose coming from a contact we are not subscribed from"); - return message; + return nil; } NSDate* delayStamp = [messageNode findFirst:@"{urn:xmpp:delay}delay@stamp|datetime"]; @@ -156,7 +156,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if([[NSDate date] timeIntervalSinceDate:delayStamp] > 60.0) { DDLogWarn(@"Ignoring incoming JMI propose: too old"); - return message; + return nil; } //only allow audio calls for now @@ -173,7 +173,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } else DDLogWarn(@"Ignoring incoming non-audio JMI call, not implemented yet"); - return message; + return nil; } //handle all other JMI events (TODO: add entry to local history, once the UI for this is implemented) //if the corresponding call is unknown these will just be ignored by MLVoipProcessor --> no presence leak @@ -192,7 +192,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //in the monal compilation unit (the ui unit), the NSE resides in yet another compilation unit (the nse-appex unit) [[MLNotificationQueue currentQueue] postNotificationName:kMonalIncomingJMIStanza object:account userInfo:callData]; } - return message; + return nil; } } @@ -205,7 +205,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag DDLogWarn(@"Ignoring muc pm marked as such..."); //ignore muc pms without id attribute (we can't send out errors pointing to this message without an id) if([messageNode findFirst:@"/@id"] == nil) - return message; + return nil; XMPPMessage* errorReply = [XMPPMessage new]; [errorReply.attributes setObject:@"error" forKey:@"type"]; [errorReply.attributes setObject:messageNode.from forKey:@"to"]; //this has to be the full jid here @@ -216,7 +216,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag ] andData:nil]]; [errorReply setStoreHint]; [account send:errorReply]; - return message; + return nil; } //ignore carbon copied muc pms not marked as such NSString* carbonType = [outerMessageNode findFirst:@"{urn:xmpp:carbons:2}*$"]; @@ -227,7 +227,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if(carbonTestContact.isGroup) { DDLogWarn(@"Ignoring carbon copied muc pm..."); - return message; + return nil; } else DDLogVerbose(@"Not a carbon copy of a muc pm for contact: %@", carbonTestContact); @@ -246,7 +246,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag { // ignore message DDLogWarn(@"Ignoring groupchat message from %@", messageNode.toUser); - return message; + return nil; } } else @@ -261,7 +261,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } else DDLogInfo(@"Ignoring MLhistory KeyTransportElement for buddy %@", possibleUnkownContact); - return message; + return nil; } } @@ -290,7 +290,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //handle muc status changes or invites (this checks for the muc namespace itself) if([account.mucProcessor processMessage:messageNode]) - return message; //the muc processor said we have stop processing + return nil; //the muc processor said we have stop processing //add contact if possible (ignore groupchats or already existing contacts, or KeyTransportElements) DDLogInfo(@"Adding possibly unknown contact for %@ to local contactlist (not updating remote roster!), doing nothing if contact is already known...", possibleUnkownContact); @@ -343,7 +343,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag DDLogInfo(@"Got MUC subject for %@: %@", messageNode.fromUser, subject); if(subject == nil || [subject isEqualToString:currentSubject]) - return message; + return nil; DDLogVerbose(@"Updating subject in database: %@", subject); [[DataLayer sharedInstance] updateMucSubject:subject forAccount:account.accountNo andRoom:messageNode.fromUser]; @@ -353,12 +353,12 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag @"subject": subject, }]; } - return message; + return nil; } //ignore all other groupchat messages coming from bare jid (e.g. not being a "normal" muc message nor a subject update handled above) if([messageNode check:@"/"] && !messageNode.fromResource) - return message; + return nil; NSString* decrypted; if([messageNode check:@"{eu.siacs.conversations.axolotl}encrypted/header"]) From c7d03f15055363dc8a273196e710be8b51a044aa Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 18 Feb 2024 03:48:39 +0100 Subject: [PATCH 029/115] Add privacy setting to ignore incoming stanzas of unknown contacts --- Monal/Classes/MLIQProcessor.m | 156 +++++++++--------- Monal/Classes/MLMessageProcessor.m | 25 +-- .../Classes/MLPrivacySettingsViewController.m | 55 +++--- Monal/Classes/MLXMPPManager.m | 3 + Monal/Classes/xmpp.m | 7 + 5 files changed, 127 insertions(+), 119 deletions(-) diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index a8d185fd06..619ab6dec7 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -26,6 +26,11 @@ @implementation MLIQProcessor +(void) processUnboundIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account { + //only handle these iqs if the remote user is on our roster + MLContact* contact = [MLContact createContactFromJid:iqNode.fromUser andAccountNo:account.accountNo]; + if(![account.connectionProperties.identity.jid isEqualToString:iqNode.fromUser] && !(contact.isSubscribedFrom && !contact.isGroup)) + DDLogWarn(@"Invalid sender for iq (!subscribedFrom || isGroup), ignoring: %@", iqNode); + if([iqNode check:@"/"]) [self processGetIq:iqNode forAccount:account]; else if([iqNode check:@"/"]) @@ -40,40 +45,33 @@ +(void) processUnboundIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account +(void) processGetIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account { - //only handle these iqs if the remote user is on our roster - MLContact* contact = [MLContact createContactFromJid:iqNode.fromUser andAccountNo:account.accountNo]; - if([account.connectionProperties.identity.jid isEqualToString:iqNode.fromUser] || (contact.isSubscribedFrom && !contact.isGroup)) + if([iqNode check:@"{urn:xmpp:ping}ping"]) { - if([iqNode check:@"{urn:xmpp:ping}ping"]) - { - XMPPIQ* pong = [[XMPPIQ alloc] initAsResponseTo:iqNode]; - [pong setiqTo:iqNode.from]; - [account send:pong]; - return; - } - - if([iqNode check:@"{jabber:iq:version}query"] && [[HelperTools defaultsDB] boolForKey: @"allowVersionIQ"]) - { - XMPPIQ* versioniq = [[XMPPIQ alloc] initAsResponseTo:iqNode]; - [versioniq setiqTo:iqNode.from]; - [versioniq setVersion]; - [account send:versioniq]; - return; - } - - if([iqNode check:@"{http://jabber.org/protocol/disco#info}query"]) - { - XMPPIQ* discoInfoResponse = [[XMPPIQ alloc] initAsResponseTo:iqNode]; - [discoInfoResponse setDiscoInfoWithFeatures:account.capsFeatures identity:account.capsIdentity andNode:[iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query@node"]]; - [account send:discoInfoResponse]; - return; - } - - DDLogWarn(@"Got unhandled get IQ: %@", iqNode); - [self respondWithErrorTo:iqNode onAccount:account]; + XMPPIQ* pong = [[XMPPIQ alloc] initAsResponseTo:iqNode]; + [pong setiqTo:iqNode.from]; + [account send:pong]; + return; } - else - DDLogWarn(@"Invalid sender for get iq (!subscribedFrom || isGroup), ignoring iq: %@", iqNode); + + if([iqNode check:@"{jabber:iq:version}query"] && [[HelperTools defaultsDB] boolForKey: @"allowVersionIQ"]) + { + XMPPIQ* versioniq = [[XMPPIQ alloc] initAsResponseTo:iqNode]; + [versioniq setiqTo:iqNode.from]; + [versioniq setVersion]; + [account send:versioniq]; + return; + } + + if([iqNode check:@"{http://jabber.org/protocol/disco#info}query"]) + { + XMPPIQ* discoInfoResponse = [[XMPPIQ alloc] initAsResponseTo:iqNode]; + [discoInfoResponse setDiscoInfoWithFeatures:account.capsFeatures identity:account.capsIdentity andNode:[iqNode findFirst:@"{http://jabber.org/protocol/disco#info}query@node"]]; + [account send:discoInfoResponse]; + return; + } + + DDLogWarn(@"Got unhandled get IQ: %@", iqNode); + [self respondWithErrorTo:iqNode onAccount:account]; } +(void) processSetIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account @@ -92,71 +90,65 @@ +(void) processSetIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account return; } - MLContact* contact = [MLContact createContactFromJid:iqNode.fromUser andAccountNo:account.accountNo]; - if([account.connectionProperties.identity.jid isEqualToString:iqNode.fromUser] || (contact.isSubscribedFrom && !contact.isGroup)) + //its a roster push (sanity check will be done in processRosterWithAccount:andIqNode:) + if([iqNode check:@"{jabber:iq:roster}query"]) { - //its a roster push (sanity check will be done in processRosterWithAccount:andIqNode:) - if([iqNode check:@"{jabber:iq:roster}query"]) + //this will only return YES, if the roster push was allowed and processed successfully + if([self processRosterWithAccount:account andIqNode:iqNode]) { - //this will only return YES, if the roster push was allowed and processed successfully - if([self processRosterWithAccount:account andIqNode:iqNode]) - { - //send empty result iq as per RFC 6121 requirements - XMPPIQ* reply = [[XMPPIQ alloc] initAsResponseTo:iqNode]; - [reply setiqTo:iqNode.from]; - [account send:reply]; - } - return; + //send empty result iq as per RFC 6121 requirements + XMPPIQ* reply = [[XMPPIQ alloc] initAsResponseTo:iqNode]; + [reply setiqTo:iqNode.from]; + [account send:reply]; } + return; + } - if([iqNode check:@"{urn:xmpp:blocking}block"] || [iqNode check:@"{urn:xmpp:blocking}unblock"]) + if([iqNode check:@"{urn:xmpp:blocking}block"] || [iqNode check:@"{urn:xmpp:blocking}unblock"]) + { + //make sure we don't process blocking updates not coming from our own account + if(account.connectionProperties.supportsBlocking && (iqNode.from == nil || [iqNode.fromUser isEqualToString:account.connectionProperties.identity.jid])) { - //make sure we don't process blocking updates not coming from our own account - if(account.connectionProperties.supportsBlocking && (iqNode.from == nil || [iqNode.fromUser isEqualToString:account.connectionProperties.identity.jid])) + BOOL blockingUpdated = NO; + // mark jid as unblocked + if([iqNode check:@"{urn:xmpp:blocking}unblock"]) { - BOOL blockingUpdated = NO; - // mark jid as unblocked - if([iqNode check:@"{urn:xmpp:blocking}unblock"]) + NSArray* unBlockItems = [iqNode find:@"{urn:xmpp:blocking}unblock/item@@"]; + for(NSDictionary* item in unBlockItems) { - NSArray* unBlockItems = [iqNode find:@"{urn:xmpp:blocking}unblock/item@@"]; - for(NSDictionary* item in unBlockItems) - { - if(item && item[@"jid"]) - [[DataLayer sharedInstance] unBlockJid:item[@"jid"] withAccountNo:account.accountNo]; - } - if(unBlockItems && unBlockItems.count == 0) - { - // remove all blocks - [account updateLocalBlocklistCache:[[NSSet alloc] init]]; - } - blockingUpdated = YES; + if(item && item[@"jid"]) + [[DataLayer sharedInstance] unBlockJid:item[@"jid"] withAccountNo:account.accountNo]; } - // mark jid as blocked - if([iqNode check:@"{urn:xmpp:blocking}block"]) + if(unBlockItems && unBlockItems.count == 0) { - for(NSDictionary* item in [iqNode find:@"{urn:xmpp:blocking}block/item@@"]) - { - if(item && item[@"jid"]) - [[DataLayer sharedInstance] blockJid:item[@"jid"] withAccountNo:account.accountNo]; - } - blockingUpdated = YES; + // remove all blocks + [account updateLocalBlocklistCache:[[NSSet alloc] init]]; } - if(blockingUpdated) + blockingUpdated = YES; + } + // mark jid as blocked + if([iqNode check:@"{urn:xmpp:blocking}block"]) + { + for(NSDictionary* item in [iqNode find:@"{urn:xmpp:blocking}block/item@@"]) { - // notify the views - [[MLNotificationQueue currentQueue] postNotificationName:kMonalBlockListRefresh object:account userInfo:@{@"accountNo": account.accountNo}]; + if(item && item[@"jid"]) + [[DataLayer sharedInstance] blockJid:item[@"jid"] withAccountNo:account.accountNo]; } + blockingUpdated = YES; + } + if(blockingUpdated) + { + // notify the views + [[MLNotificationQueue currentQueue] postNotificationName:kMonalBlockListRefresh object:account userInfo:@{@"accountNo": account.accountNo}]; } - else - DDLogWarn(@"Invalid sender for blocklist, ignoring iq: %@", iqNode); - return; } - - DDLogWarn(@"Got unhandled set IQ: %@", iqNode); - [self respondWithErrorTo:iqNode onAccount:account]; + else + DDLogWarn(@"Invalid sender for blocklist, ignoring iq: %@", iqNode); + return; } - else - DDLogWarn(@"Invalid sender for set iq (!subscribedFrom || isGroup), ignoring iq: %@", iqNode); + + DDLogWarn(@"Got unhandled set IQ: %@", iqNode); + [self respondWithErrorTo:iqNode onAccount:account]; } +(void) processResultIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index f4d3159bdf..b999090f14 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -97,6 +97,16 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag return nil; } + NSString* buddyName = [messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid] ? messageNode.toUser : messageNode.fromUser; + MLContact* possiblyUnknownContact = [MLContact createContactFromJid:buddyName andAccountNo:account.accountNo]; + + //ignore unknown contacts if configured to do so + if(![[HelperTools defaultsDB] boolForKey: @"allowNonRosterContacts"] && !possiblyUnknownContact.isSubscribedFrom) + { + DDLogWarn(@"Ignoring incoming message stanza from unknown contact: %@", possiblyUnknownContact); + return nil; + } + //ignore prosody mod_muc_notifications muc push stanzas (they are only needed to trigger an apns push) //but trigger a muc ping for these mucs nonetheless (if this muc is known, we don't want to arbitrarily join mucs just because of this stanza) if([messageNode check:@"{http://quobis.com/xmpp/muc#push}notification"]) @@ -232,12 +242,6 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag else DDLogVerbose(@"Not a carbon copy of a muc pm for contact: %@", carbonTestContact); } - - NSString* possibleUnkownContact; - if([messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid]) - possibleUnkownContact = messageNode.toUser; - else - possibleUnkownContact = messageNode.fromUser; if(([messageNode check:@"/"] || [messageNode check:@"{http://jabber.org/protocol/muc#user}x"]) && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) { @@ -256,11 +260,11 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag { if(!isMLhistory) { - DDLogInfo(@"Handling KeyTransportElement without trying to add a 1:1 buddy %@", possibleUnkownContact); + DDLogInfo(@"Handling KeyTransportElement without trying to add a 1:1 buddy %@", possiblyUnknownContact); [account.omemo decryptMessage:messageNode withMucParticipantJid:nil]; } else - DDLogInfo(@"Ignoring MLhistory KeyTransportElement for buddy %@", possibleUnkownContact); + DDLogInfo(@"Ignoring MLhistory KeyTransportElement for buddy %@", possiblyUnknownContact); return nil; } } @@ -293,10 +297,9 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag return nil; //the muc processor said we have stop processing //add contact if possible (ignore groupchats or already existing contacts, or KeyTransportElements) - DDLogInfo(@"Adding possibly unknown contact for %@ to local contactlist (not updating remote roster!), doing nothing if contact is already known...", possibleUnkownContact); - [[DataLayer sharedInstance] addContact:possibleUnkownContact forAccount:account.accountNo nickname:nil]; + DDLogInfo(@"Adding possibly unknown contact for %@ to local contactlist (not updating remote roster!), doing nothing if contact is already known...", possiblyUnknownContact); + [[DataLayer sharedInstance] addContact:possiblyUnknownContact.contactJid forAccount:account.accountNo nickname:nil]; - NSString* buddyName = [messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid] ? messageNode.toUser : messageNode.fromUser; NSString* ownNick; NSString* actualFrom = messageNode.fromUser; NSString* participantJid = nil; diff --git a/Monal/Classes/MLPrivacySettingsViewController.m b/Monal/Classes/MLPrivacySettingsViewController.m index 0364417b9d..2be7104b14 100644 --- a/Monal/Classes/MLPrivacySettingsViewController.m +++ b/Monal/Classes/MLPrivacySettingsViewController.m @@ -59,31 +59,32 @@ - (void)viewWillDisappear:(BOOL)animated #pragma mark tableview datasource delegate --(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +-(NSInteger) numberOfSectionsInTableView:(UITableView*) tableView { return [self.sectionArray count]; } -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +-(NSString*) tableView:(UITableView*) tableView titleForHeaderInSection:(NSInteger) section { return [self.sectionArray objectAtIndex:section]; } --(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section +-(UIView*) tableView:(UITableView*) tableView viewForHeaderInSection:(NSInteger) section { NSString* sectionTitle = [self tableView:tableView titleForHeaderInSection:section]; return [HelperTools MLCustomViewHeaderWithTitle:sectionTitle]; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +-(NSInteger) tableView:(UITableView*) tableView numberOfRowsInSection:(NSInteger) section { - switch (section) { + switch (section) + { case 0: { #ifdef DISABLE_OMEMO - return 12 + (self.isNotificationPrivacyOpened ? NotificationPrivacyOptionCnt : 0); -#else// DISABLE_OMEMO return 13 + (self.isNotificationPrivacyOpened ? NotificationPrivacyOptionCnt : 0); +#else// DISABLE_OMEMO + return 14 + (self.isNotificationPrivacyOpened ? NotificationPrivacyOptionCnt : 0); #endif// DISABLE_OMEMO } default: @@ -94,13 +95,14 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } } -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +-(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath { - MLSwitchCell* cell = (MLSwitchCell *)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; + MLSwitchCell* cell = (MLSwitchCell*)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; [cell clear]; - switch (indexPath.section) { + switch(indexPath.section) + { case 0: { long row = indexPath.row; @@ -197,6 +199,11 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; #endif// DISABLE_OMEMO } + case 16: + { + [cell initCell:NSLocalizedString(@"Allow contacts not in my Contact list to contact me", @"") withToggleDefaultsKey:@"allowNonRosterContacts"]; + break; + } default: unreachable(); break; @@ -210,9 +217,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +-(void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath { - switch (indexPath.section) { + switch (indexPath.section) + { case 0: { long row = indexPath.row; @@ -252,6 +260,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case 13: case 14: case 15: + case 16: break; } break; @@ -268,25 +277,18 @@ -(IBAction)close:(id)sender [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; } --(void)openNotificationPrivacyFolder +-(void) openNotificationPrivacyFolder { - if (self.isNotificationPrivacyOpened) - { - self.isNotificationPrivacyOpened = NO; - } - else - { - self.isNotificationPrivacyOpened = YES; - } + self.isNotificationPrivacyOpened = !self.isNotificationPrivacyOpened; [self refershTable]; } --(void)refershTable +-(void) refershTable { [_settingsTable reloadData]; } --(void)checkStatusForCell:(MLSwitchCell*) cell atIndexPath:(NSIndexPath*) idxPath +-(void) checkStatusForCell:(MLSwitchCell*) cell atIndexPath:(NSIndexPath*) idxPath { NotificationPrivacySettingOption privacySettionOption = (NotificationPrivacySettingOption)[[HelperTools defaultsDB] integerForKey:@"NotificationPrivacySetting"]; // default: remove checkmark @@ -318,8 +320,9 @@ -(void)checkStatusForCell:(MLSwitchCell*) cell atIndexPath:(NSIndexPath*) idxPat -(NSString*)getNsNotificationPrivacyOption:(NotificationPrivacySettingOption) option { - NSString *optionStr = @""; - switch (option) { + NSString* optionStr = @""; + switch (option) + { case DisplayNameAndMessage: optionStr = NSLocalizedString(@"Display Name And Message", @""); break; @@ -336,7 +339,7 @@ -(NSString*)getNsNotificationPrivacyOption:(NotificationPrivacySettingOption) op return optionStr; } --(void)setNotificationPrivacyOption:(NSIndexPath*) idxPath +-(void) setNotificationPrivacyOption:(NSIndexPath*) idxPath { switch (idxPath.row) { case DisplayNameAndMessageRow: diff --git a/Monal/Classes/MLXMPPManager.m b/Monal/Classes/MLXMPPManager.m index 14bbbd17ab..0601068739 100644 --- a/Monal/Classes/MLXMPPManager.m +++ b/Monal/Classes/MLXMPPManager.m @@ -139,6 +139,9 @@ -(void) defaultSettings //default value for sanbox is no (e.g. production) [self upgradeBoolUserSettingsIfUnset:@"isSandboxAPNS" toDefault:NO]; + + //anti spam/privacy setting, but default to yes (current behavior, conversations behavior etc.) + [self upgradeBoolUserSettingsIfUnset:@"allowNonRosterContacts" toDefault:YES]; } -(void) upgradeBoolUserSettingsIfUnset:(NSString*) settingsName toDefault:(BOOL) defaultVal diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 8f23b5ffab..e9980d174d 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -1861,6 +1861,13 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR [self incrementLastHandledStanzaWithDelayedReplay:delayedReplay]; return; } + + if(![[HelperTools defaultsDB] boolForKey: @"allowNonRosterContacts"] && !contact.isSubscribedFrom) + { + //mark this stanza as handled + [self incrementLastHandledStanzaWithDelayedReplay:delayedReplay]; + return; + } if(![presenceNode check:@"/@type"]) { From 037b6bb61eaf7dcce0522762f2718653a35463e6 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 18 Feb 2024 04:11:13 +0100 Subject: [PATCH 030/115] Fix crash on iPads when doing reconnect all --- Monal/Classes/chatViewController.m | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index ccc9352bd4..efc9cb39df 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -485,11 +485,13 @@ -(void) setChatInputHeightConstraints:(BOOL) hwKeyboardPresent -(void) handleForeGround { - @synchronized(_localMLContactCache) { - [_localMLContactCache removeAllObjects]; - } - [self refreshData]; - [self reloadTable]; + dispatch_async(dispatch_get_main_queue(), ^{ + @synchronized(self->_localMLContactCache) { + [self->_localMLContactCache removeAllObjects]; + } + [self refreshData]; + [self reloadTable]; + }); } -(void) openCallScreen:(id) sender @@ -903,13 +905,15 @@ -(void) dealloc -(void) handleBackgroundChanged { - DDLogVerbose(@"Loading background image for %@", self.contact); - self.backgroundImage.image = [[MLImageManager sharedInstance] getBackgroundFor:self.contact]; - //use default background if this contact does not have its own - if(self.backgroundImage.image == nil) - self.backgroundImage.image = [[MLImageManager sharedInstance] getBackgroundFor:nil]; - self.backgroundImage.hidden = self.backgroundImage.image == nil; - DDLogVerbose(@"Background is now: %@", self.backgroundImage.image); + dispatch_async(dispatch_get_main_queue(), ^{ + DDLogVerbose(@"Loading background image for %@", self.contact); + self.backgroundImage.image = [[MLImageManager sharedInstance] getBackgroundFor:self.contact]; + //use default background if this contact does not have its own + if(self.backgroundImage.image == nil) + self.backgroundImage.image = [[MLImageManager sharedInstance] getBackgroundFor:nil]; + self.backgroundImage.hidden = self.backgroundImage.image == nil; + DDLogVerbose(@"Background is now: %@", self.backgroundImage.image); + }); } #pragma mark rotation From 1822178d0bc042ed0f9c2f4afd7c0df86e71c1d5 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 19 Feb 2024 00:33:02 +0100 Subject: [PATCH 031/115] Honor dark mode for rich alerts, thanks lissine --- Monal/Classes/RichAlert.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/RichAlert.swift b/Monal/Classes/RichAlert.swift index eacc0b4f9a..fbe758efa9 100644 --- a/Monal/Classes/RichAlert.swift +++ b/Monal/Classes/RichAlert.swift @@ -59,7 +59,7 @@ struct RichAlertView: ViewModifier DispatchQueue.main.async { scrollViewContentSize = geo.size } - return Color.white + return Color.background } ) } @@ -67,9 +67,9 @@ struct RichAlertView: ViewModifier } .padding([.top, .bottom], 13) .frame(width: 320) - .background(Color.white) + .background(Color.background) .cornerRadius(16) - .shadow(color: Color.black.opacity(0.4), radius: 16, x: 0, y: 0) + .shadow(color: Color.primary.opacity(0.4), radius: 16, x: 0, y: 0) .padding([.top, .bottom], 24) } } From f6df4c6107b8bd56dd685936a319dd9dc2e95067 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:23:32 +0100 Subject: [PATCH 032/115] Show affiliation in member lists --- Monal/Classes/ChannelMemberList.swift | 62 ++++++++++++++++++++ Monal/Classes/ContactDetails.swift | 4 ++ Monal/Classes/MemberList.swift | 81 +++++++++++++++++---------- Monal/Monal.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 121 insertions(+), 30 deletions(-) create mode 100644 Monal/Classes/ChannelMemberList.swift diff --git a/Monal/Classes/ChannelMemberList.swift b/Monal/Classes/ChannelMemberList.swift new file mode 100644 index 0000000000..d5f9c41878 --- /dev/null +++ b/Monal/Classes/ChannelMemberList.swift @@ -0,0 +1,62 @@ +// +// ChannelMemberList.swift +// Monal +// +// Created by Friedrich Altheide on 17.02.24. +// Copyright © 2024 monal-im.org. All rights reserved. +// + +import SwiftUI +import monalxmpp +import OrderedCollections + +struct ChannelMemberList: View { + @State private var channelMembers: OrderedDictionary + @ObservedObject var channel: ObservableKVOWrapper + private let account: xmpp? + + var body: some View { + List { + Section(header: Text(self.channel.obj.contactDisplayName)) { + ForEach(self.channelMembers.sorted(by: <), id: \.self.key) { + member in + ZStack(alignment: .topLeading) { + HStack(alignment: .center) { + Text(member.key) + Spacer() + if member.value == "owner" { + Text(NSLocalizedString("Owner", comment: "")) + } else if member.value == "admin" { + Text(NSLocalizedString("Admin", comment: "")) + } else { + Text(NSLocalizedString("Member", comment: "")) + } + } + } + } + } + } + .navigationBarTitle("Channel Members", displayMode: .inline) + } + + init(channelContact: ObservableKVOWrapper) { + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: channelContact.obj.accountId)! as xmpp + self.channel = channelContact; + + let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: channelContact.obj.contactJid, forAccountId: channelContact.obj.accountId)) + var nickSet : OrderedDictionary = OrderedDictionary() + for jidDict in jidList { + if let nick = jidDict["room_nick"] as? String { + nickSet.updateValue(jidDict["affiliation"]! as! String, forKey: nick) + } + } + _channelMembers = State(wrappedValue: nickSet) + } +} + +/*struct ChannelMemberList_Previews: PreviewProvider { + static var previews: some View { + // TODO some dummy views, requires a dummy xmpp obj + // ChannelMemberList(channelContact: nil); + } +}*/ diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index a8aea957b3..f49149e585 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -125,6 +125,10 @@ struct ContactDetails: View { NavigationLink(destination: LazyClosureView(MemberList(mucContact: contact))) { Text("Group Members") } + } else if(contact.obj.isGroup && contact.obj.mucType == "channel") { + NavigationLink(destination: LazyClosureView(ChannelMemberList(channelContact: contact))) { + Text("Channel Members") + } } #if !DISABLE_OMEMO if(!HelperTools.isContactBlacklisted(forEncryption:contact.obj)) { diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 844b77ad53..eb313e54fe 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -11,11 +11,11 @@ import monalxmpp import OrderedCollections struct MemberList: View { - @Environment(\.editMode) private var editMode - @State private var memberList: [ObservableKVOWrapper] + @State private var affiliation: Dictionary @ObservedObject var group: ObservableKVOWrapper private let account: xmpp? + private var ownAffiliation: String = "none"; @State private var openAccountSelection : Bool = false @State private var contactsToAdd : OrderedSet = [] @@ -28,6 +28,20 @@ struct MemberList: View { self.showAlert = true } + func ownUserHasPermissionToRemove(contact: ObservableKVOWrapper) -> Bool { + if contact.obj.contactJid == self.account?.connectionProperties.identity.jid { + return false + } + if let contactAffiliation = self.affiliation[contact.obj.contactJid] { + if self.ownAffiliation == "owner" { + return true + } else if self.ownAffiliation == "admin" && contactAffiliation == "member" { + return true + } + } + return false + } + var body: some View { // This is the invisible NavigationLink hack again... NavigationLink(destination:LazyClosureView(ContactPicker(account: self.account!, selectedContacts: $contactsToAdd)), isActive: $openAccountSelection){}.hidden().disabled(true) // navigation happens as soon as our button sets navigateToQRCodeView to true... @@ -35,24 +49,29 @@ struct MemberList: View { Section(header: Text(self.group.obj.contactDisplayName)) { ForEach(self.memberList, id: \.self.obj) { contact in - if contact.obj.contactJid != self.account?.connectionProperties.identity.jid { - NavigationLink(destination: LazyClosureView(ContactDetails(delegate: SheetDismisserProtocol(), contact: contact)), label: { - ZStack(alignment: .topLeading) { - HStack(alignment: .center) { - Image(uiImage: contact.obj.avatar) - .resizable() - .frame(width: 40, height: 40, alignment: .center) - Text(contact.contactDisplayName as String) - if(editMode?.wrappedValue.isEditing == true) { - Spacer() - Button(action: {}, label: { - Image(systemName: "slider.horizontal.3") - }) + NavigationLink(destination: LazyClosureView(ContactDetails(delegate: SheetDismisserProtocol(), contact: contact)), label: { + ZStack(alignment: .topLeading) { + HStack(alignment: .center) { + Image(uiImage: contact.obj.avatar) + .resizable() + .frame(width: 40, height: 40, alignment: .center) + Text(contact.contactDisplayName as String) + Spacer() + if let contactAffiliation = self.affiliation[contact.obj.contactJid] { + if contactAffiliation == "owner" { + Text(NSLocalizedString("Owner", comment: "")) + } else if contactAffiliation == "admin" { + Text(NSLocalizedString("Admin", comment: "")) + } else { + Text(NSLocalizedString("Member", comment: "")) } } } - }) - } + } + }) + .deleteDisabled( + !ownUserHasPermissionToRemove(contact: contact) + ) } .onDelete(perform: { memberIdx in let member = self.memberList[memberIdx.first!] @@ -65,19 +84,6 @@ struct MemberList: View { Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton: .default(alertPrompt.dismissLabel)) }) } - .toolbar { -#if IS_ALPHA - if(editMode?.wrappedValue.isEditing == true) { - Button(action: { - openAccountSelection = true - }, label: { - Image(systemName: "plus") - .foregroundColor(.blue) - }) - } -#endif - EditButton() - } .navigationBarTitle("Group Members", displayMode: .inline) } @@ -85,6 +91,21 @@ struct MemberList: View { self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact!.obj.accountId)! as xmpp self.group = mucContact!; _memberList = State(wrappedValue: getContactList(viewContact: mucContact)) + var affiliationTmp = Dictionary() + for memberInfo in Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact!.contactJid, forAccountId: self.account!.accountNo)) { + var jid : String? = memberInfo["participant_jid"] as? String + if(jid == nil) { + jid = memberInfo["member_jid"] as? String + } + if(jid == nil) { + continue + } + if(jid == self.account?.connectionProperties.identity.jid) { + self.ownAffiliation = memberInfo["affiliation"]! as! String + } + affiliationTmp.updateValue(memberInfo["affiliation"]! as! String, forKey: jid!) + } + _affiliation = State(wrappedValue: affiliationTmp) } } diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index df4275fcdb..434cf89e38 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -187,6 +187,7 @@ C176F1EC2AF11C31002034E5 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C176F1EB2AF11C31002034E5 /* UserNotifications.framework */; }; C1850EB825F38A2D003D506A /* MonalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1850EB725F38A2D003D506A /* MonalUITests.swift */; }; C1850EC625F3C5EB003D506A /* TestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1850EC525F3C5EB003D506A /* TestHelper.swift */; }; + C18967C72B81F61B0073C7C5 /* ChannelMemberList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18967C62B81F61B0073C7C5 /* ChannelMemberList.swift */; }; C18E757A245E8AE900AE8FB7 /* MLPipe.h in Headers */ = {isa = PBXBuildFile; fileRef = C18E7578245E8AE900AE8FB7 /* MLPipe.h */; }; C18E757C245E8AE900AE8FB7 /* MLPipe.m in Sources */ = {isa = PBXBuildFile; fileRef = C18E7579245E8AE900AE8FB7 /* MLPipe.m */; }; C1943A4C25309A9D0036172F /* MLReloadCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C1943A4B25309A9D0036172F /* MLReloadCell.m */; }; @@ -656,6 +657,7 @@ C1850EB725F38A2D003D506A /* MonalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonalUITests.swift; sourceTree = ""; }; C1850EB925F38A2D003D506A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C1850EC525F3C5EB003D506A /* TestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = ""; }; + C18967C62B81F61B0073C7C5 /* ChannelMemberList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMemberList.swift; sourceTree = ""; }; C18E7578245E8AE900AE8FB7 /* MLPipe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLPipe.h; sourceTree = ""; }; C18E7579245E8AE900AE8FB7 /* MLPipe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLPipe.m; sourceTree = ""; }; C1943A4A25309A9D0036172F /* MLReloadCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLReloadCell.h; sourceTree = ""; }; @@ -1098,6 +1100,7 @@ 3D65B78C27234B74005A30F4 /* ContactDetails.swift */, C19C919A26E26AF000F8CC57 /* ContactDetailsHeader.swift */, 3D5A91412842B4AE008CE57E /* MemberList.swift */, + C18967C62B81F61B0073C7C5 /* ChannelMemberList.swift */, ); name = "Contact Details"; sourceTree = ""; @@ -2024,6 +2027,7 @@ 848717F3295ED64600B8D288 /* MLCall.m in Sources */, 26D89F061A890672009B147C /* MLSwitchCell.m in Sources */, C13EBB8E24DC685C008AADDA /* MLPrivacySettingsViewController.m in Sources */, + C18967C72B81F61B0073C7C5 /* ChannelMemberList.swift in Sources */, C114D13D2B15B903000FB99F /* ContactEntry.swift in Sources */, 841B6F1A297B18720074F9B7 /* AccountPicker.swift in Sources */, 3D65B791272350F0005A30F4 /* SwiftuiHelpers.swift in Sources */, From f7bdabb2fa4fe050aeb705d8926ac7ebe0980d5a Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:25:11 +0100 Subject: [PATCH 033/115] bump MARKETING_VERSION to 6.1.2 --- Monal/Monal.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index 434cf89e38..eb48776270 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -2630,7 +2630,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.1.2; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; @@ -2963,7 +2963,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.1.2; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; @@ -3114,7 +3114,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.1.2; ONLY_ACTIVE_ARCH = YES; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; @@ -3374,7 +3374,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.1.2; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; @@ -3698,7 +3698,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.1.2; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = IS_QUICKSY; @@ -4074,7 +4074,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = NO; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.1; + MARKETING_VERSION = 6.1.2; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; From 3ad52ec466dcd27b5de01f5cf7404d473f185a98 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 19 Feb 2024 19:31:20 +0100 Subject: [PATCH 034/115] Fix muc processor not adding bookmark for newly created muc --- Monal/Classes/MLMucProcessor.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 079e9da7a6..927cc1a57f 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -529,7 +529,6 @@ -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) ma { //group is now properly configured and we are joined, but all the code handling a proper join was not run //--> join again to make sure everything is sane - [self removeRoomFromCreating:roomJid]; [self join:roomJid]; } $$ @@ -645,6 +644,8 @@ -(void) handleStatusCodes:(XMPPStanza*) node } break; } + case 110: + break; //ignore self-presence status handled below default: DDLogWarn(@"Got unhandled muc status code in presence from %@: %@", node.from, code); } @@ -1088,6 +1089,11 @@ -(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room @"roomJid": [NSString stringWithFormat:@"%@", roomJid], })); + //no matter what the disco response is: we are not creating this muc anymore + //either because we successfully created it and called join afterwards, + //or because the user tried to simultaneously create and join this muc (the join has precendence in this case) + [self removeRoomFromCreating:roomJid]; + if([iqNode check:@"//error/{urn:ietf:params:xml:ns:xmpp-stanzas}gone"]) { DDLogError(@"Querying muc info returned this muc isn't available anymore: %@", [iqNode findFirst:@"error"]); From 50f80096ffb3b26e369afc64121c49110431be1d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 19 Feb 2024 19:55:43 +0100 Subject: [PATCH 035/115] Handle muc status codes allowed in both presences and messages --- Monal/Classes/DataLayer.m | 1 + Monal/Classes/MLMucProcessor.m | 78 ++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index d5a83750a1..db92907896 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -1014,6 +1014,7 @@ -(void) removeMember:(NSDictionary*) member fromMuc:(NSString*) room forAccountI if(!member || !member[@"jid"] || !room || accountNo == nil) return; + DDLogDebug(@"Removing member '%@' from muc '%@'...", member[@"jid"], room); [self.db voidWriteTransaction:^{ [self.db executeNonQuery:@"DELETE FROM muc_members WHERE account_id=? AND room=? AND member_jid=?;" andArguments:@[accountNo, room, member[@"jid"]]]; }]; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 927cc1a57f..1bb8d2be9c 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -537,36 +537,14 @@ -(void) handleStatusCodes:(XMPPStanza*) node { NSSet* presenceCodes = [[NSSet alloc] initWithArray:[node find:@"/{jabber:client}presence/{http://jabber.org/protocol/muc#user}x/status@code|int"]]; NSSet* messageCodes = [[NSSet alloc] initWithArray:[node find:@"/{jabber:client}message/{http://jabber.org/protocol/muc#user}x/status@code|int"]]; + NSString* nick = [[DataLayer sharedInstance] ownNickNameforMuc:node.fromUser forAccount:_account.accountNo]; - //handle presence stanzas - if(presenceCodes && [presenceCodes count]) - { - NSString* nick = [[DataLayer sharedInstance] ownNickNameforMuc:node.fromUser forAccount:_account.accountNo]; - for(NSNumber* code in presenceCodes) + //handle status codes allowed in presences AND messages + NSMutableSet* unhandledStatusCodes = [NSMutableSet new]; + NSMutableSet* jointCodes = [[presenceCodes mutableCopy] unionSet:messageCodes]; + for(NSNumber* code in ) switch([code intValue]) { - //room created and needs configuration now - case 201: - { - if(![presenceCodes containsObject:@110]) - { - DDLogError(@"Got 'muc needs configuration' status code (201) without self-presence, ignoring!"); - break; - } - if(![self isCreating:node.fromUser]) - { - DDLogError(@"Got 'muc needs configuration' status code (201) without this muc currently being created, ignoring: %@", node.fromUser); - break; - } - - //now configure newly created locked room - [self configureMuc:node.fromUser withMandatoryOptions:_mandatoryGroupConfigOptions andOptionalOptions:_optionalGroupConfigOptions deletingMucOnError:YES andJoiningMucOnSuccess:YES]; - - //stop processing here to not trigger the "successful join" code below - //we will trigger this code by a "second" join presence once the room was created and is not locked anymore - return; - break; - } //muc service changed our nick case 210: { @@ -607,7 +585,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node case 321: { //only handle this and rejoin, if we did not get removed from a members-only room - if(![presenceCodes containsObject:@322]) + if(![jointCodes containsObject:@322]) { DDLogDebug(@"user '%@' got affiliation changed for room %@", node.fromResource, node.fromUser); if([nick isEqualToString:node.fromResource]) @@ -644,10 +622,45 @@ -(void) handleStatusCodes:(XMPPStanza*) node } break; } + default: + [unhandledStatusCodes addObject:code]; + } + + //handle presence stanzas + if(presenceCodes && [presenceCodes count]) + { + for(NSNumber* code in presenceCodes) + switch([code intValue]) + { + //room created and needs configuration now + case 201: + { + if(![presenceCodes containsObject:@110]) + { + DDLogError(@"Got 'muc needs configuration' status code (201) without self-presence, ignoring!"); + break; + } + if(![self isCreating:node.fromUser]) + { + DDLogError(@"Got 'muc needs configuration' status code (201) without this muc currently being created, ignoring: %@", node.fromUser); + break; + } + + //now configure newly created locked room + [self configureMuc:node.fromUser withMandatoryOptions:_mandatoryGroupConfigOptions andOptionalOptions:_optionalGroupConfigOptions deletingMucOnError:YES andJoiningMucOnSuccess:YES]; + + //stop processing here to not trigger the "successful join" code below + //we will trigger this code by a "second" join presence once the room was created and is not locked anymore + return; + break; + } case 110: break; //ignore self-presence status handled below default: - DDLogWarn(@"Got unhandled muc status code in presence from %@: %@", node.from, code); + //only log errors for status codes not already handled by our joint handling above + if([unhandledStatusCodes containsObject:code]) + DDLogWarn(@"Got unhandled muc status code in presence from %@: %@", node.from, code); + break; } //this is a self-presence (marking the end of the presence flood if we are in joining state) @@ -753,7 +766,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node } } //handle message stanzas - else if([[node findFirst:@"/@type"] isEqualToString:@"groupchat"] && messageCodes && [messageCodes count]) + else if(messageCodes && [messageCodes count]) { for(NSNumber* code in messageCodes) switch([code intValue]) @@ -778,7 +791,10 @@ -(void) handleStatusCodes:(XMPPStanza*) node break; } default: - DDLogWarn(@"Got unhandled muc status code in message from %@: %@", node.from, code); + //only log errors for status codes not already handled by our joint handling above + if([unhandledStatusCodes containsObject:code]) + DDLogWarn(@"Got unhandled muc status code in message from %@: %@", node.from, code); + break; } } } From d434a15a7cf43ac5c666dc5ea545f32e15b5de4a Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:02:06 +0100 Subject: [PATCH 036/115] fix build --- Monal/Classes/MLMucProcessor.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 1bb8d2be9c..e2a1c81826 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -541,8 +541,9 @@ -(void) handleStatusCodes:(XMPPStanza*) node //handle status codes allowed in presences AND messages NSMutableSet* unhandledStatusCodes = [NSMutableSet new]; - NSMutableSet* jointCodes = [[presenceCodes mutableCopy] unionSet:messageCodes]; - for(NSNumber* code in ) + NSMutableSet* jointCodes = [presenceCodes mutableCopy]; + [jointCodes unionSet:messageCodes]; + for(NSNumber* code in jointCodes) switch([code intValue]) { //muc service changed our nick From 6c4d3ba0a01e6e71c49dc58fce170028a061edb3 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 19 Feb 2024 20:04:05 +0100 Subject: [PATCH 037/115] Fix incomplete muc bookmark on create bug --- Monal/Classes/MLMucProcessor.m | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index e2a1c81826..82753e8922 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -1109,8 +1109,10 @@ -(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room //no matter what the disco response is: we are not creating this muc anymore //either because we successfully created it and called join afterwards, //or because the user tried to simultaneously create and join this muc (the join has precendence in this case) + BOOL wasCreating = [self isCreating:roomJid]; [self removeRoomFromCreating:roomJid]; + if([iqNode check:@"//error/{urn:ietf:params:xml:ns:xmpp-stanzas}gone"]) { DDLogError(@"Querying muc info returned this muc isn't available anymore: %@", [iqNode findFirst:@"error"]); @@ -1192,15 +1194,19 @@ -(void) publishAvatar:(UIImage* _Nullable) image forMuc:(NSString*) room mucType = @"group"; //update db with new infos - if(![[DataLayer sharedInstance] isBuddyMuc:iqNode.fromUser forAccount:_account.accountNo]) + BOOL isBuddyMuc = [[DataLayer sharedInstance] isBuddyMuc:iqNode.fromUser forAccount:_account.accountNo]; + if(!isBuddyMuc || wasCreating) { - //remove old non-muc contact from contactlist (we don't want mucs as normal contacts on our (server) roster and shadowed in monal by the real muc contact) - NSDictionary* existingContactDict = [[DataLayer sharedInstance] contactDictionaryForUsername:iqNode.fromUser forAccount:_account.accountNo]; - if(existingContactDict != nil) + if(!isBuddyMuc) { - MLContact* existingContact = [MLContact createContactFromJid:iqNode.fromUser andAccountNo:_account.accountNo]; - DDLogVerbose(@"Removing already existing contact (%@) having raw db dict: %@", existingContact, existingContactDict); - [_account removeFromRoster:existingContact]; + //remove old non-muc contact from contactlist (we don't want mucs as normal contacts on our (server) roster and shadowed in monal by the real muc contact) + NSDictionary* existingContactDict = [[DataLayer sharedInstance] contactDictionaryForUsername:iqNode.fromUser forAccount:_account.accountNo]; + if(existingContactDict != nil) + { + MLContact* existingContact = [MLContact createContactFromJid:iqNode.fromUser andAccountNo:_account.accountNo]; + DDLogVerbose(@"Removing already existing contact (%@) having raw db dict: %@", existingContact, existingContactDict); + [_account removeFromRoster:existingContact]; + } } //add new muc buddy (potentially deleting a non-muc buddy having the same jid) NSString* nick = [self calculateNickForMuc:iqNode.fromUser]; From b936ee9f87e21be9e21b5699de7ca16a334176b4 Mon Sep 17 00:00:00 2001 From: lissine Date: Mon, 19 Feb 2024 21:21:37 +0100 Subject: [PATCH 038/115] Use built-in share functionality for generated xmpp invitations --- Monal/Classes/AddContactMenu.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/AddContactMenu.swift b/Monal/Classes/AddContactMenu.swift index 4f6d13ad29..76cef43cbb 100644 --- a/Monal/Classes/AddContactMenu.swift +++ b/Monal/Classes/AddContactMenu.swift @@ -271,8 +271,12 @@ struct AddContactMenu: View { UIPasteboard.general.setValue(data["landing"] as! String, forPasteboardType:UTType.utf8PlainText.identifier as String) invitationResult = nil }) { - Text("Copy link to clipboard") - .frame(maxWidth: .infinity) + if #available(iOS 16, *) { + ShareLink("Share invitation link", item: URL(string: data["landing"] as! String)!) + } else { + Text("Copy invitation link to clipboard") + .frame(maxWidth: .infinity) + } } Button(action: { invitationResult = nil From 0c8e5e9b2bdfc12d6bdc0a43c377046bb246574c Mon Sep 17 00:00:00 2001 From: lissine Date: Mon, 19 Feb 2024 21:23:57 +0100 Subject: [PATCH 039/115] generated invitations: Display QR code instead of link --- Monal/Classes/AddContactMenu.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Monal/Classes/AddContactMenu.swift b/Monal/Classes/AddContactMenu.swift index 76cef43cbb..b597e2ac2e 100644 --- a/Monal/Classes/AddContactMenu.swift +++ b/Monal/Classes/AddContactMenu.swift @@ -245,14 +245,12 @@ struct AddContactMenu: View { } .richAlert(isPresented: $invitationResult, title:Text("Invitation for \(splitJid["host"]!) created")) { data in VStack { - Text("Direct your buddy to this webpage for instructions on how to setup an xmpp client. You will then automatically be added to their contact list.") - .multilineTextAlignment(.leading) - .frame(maxWidth: .infinity, alignment: .leading) - Link(data["landing"] as! String, destination:URL(string:data["landing"] as! String)!) - .multilineTextAlignment(.leading) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 10) - .padding(.bottom, 10) + Image(uiImage: createQrCode(value: data["landing"] as! String)) + .interpolation(.none) + .resizable() + .scaledToFit() + .aspectRatio(1, contentMode: .fit) + if let expires = data["expires"] as? Date { HStack { if #available(iOS 15, *) { From 73f5a3258efabfd3ef8b882a08f33db1230d9b96 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 16:52:33 +0100 Subject: [PATCH 040/115] Fix monal-internal namespace --- Monal/Classes/MLPubSubProcessor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/MLPubSubProcessor.m b/Monal/Classes/MLPubSubProcessor.m index a49e6db441..a5671ad41b 100644 --- a/Monal/Classes/MLPubSubProcessor.m +++ b/Monal/Classes/MLPubSubProcessor.m @@ -381,7 +381,7 @@ @implementation MLPubSubProcessor } andChildren:@[ nilWrapper(nick != nil ? [[MLXMLNode alloc] initWithElement:@"nick" withAttributes:@{} andChildren:@[] andData:nick] : nil), [[MLXMLNode alloc] initWithElement:@"extensions" withAttributes:@{} andChildren:@[ - [[MLXMLNode alloc] initWithElement:@"added-by" andNamespace:@"urn:xmpp:monal.im:bookmarks:info" withAttributes:@{ + [[MLXMLNode alloc] initWithElement:@"added-by" andNamespace:@"urn:monal.im:bookmarks:info" withAttributes:@{ @"name": @"Monal", @"version": infoDict[@"CFBundleShortVersionString"], @"build": infoDict[@"CFBundleVersion"], From 24e1bc6bb367a5891d01e6fbabfa10565048b5f2 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 17:25:15 +0100 Subject: [PATCH 041/115] Simplify muc presence handling xmpp.m already checks for isBuddyMuc, no need to check for isContactInList again (this is even a lower requirement than isBuddyMuc). --- Monal/Classes/MLMucProcessor.m | 59 +++++++++++++++------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 82753e8922..87ff29b0b4 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -53,6 +53,7 @@ +(void) initialize @"muc#roomconfig_persistentroom": @"1", @"muc#roomconfig_membersonly": @"1", @"muc#roomconfig_whois": @"anyone", + //TODO: mark mam as mandatory }; _optionalGroupConfigOptions = @{ @"muc#roomconfig_enablelogging": @"0", @@ -259,33 +260,29 @@ -(void) processPresence:(XMPPPresence*) presenceNode if(presenceNode.fromResource == nil) { DDLogVerbose(@"Got muc presence from bare jid: %@", presenceNode.from); - if([[DataLayer sharedInstance] isContactInList:presenceNode.fromUser forAccount:_account.accountNo]) + //check vcard hash + NSString* avatarHash = [presenceNode findFirst:@"{vcard-temp:x:update}x/photo#"]; + NSString* currentHash = [[DataLayer sharedInstance] getAvatarHashForContact:presenceNode.fromUser andAccount:_account.accountNo]; + DDLogVerbose(@"Checking if avatar hash in presence '%@' equals stored hash '%@'...", avatarHash, currentHash); + if(avatarHash != nil && !(currentHash && [avatarHash isEqualToString:currentHash])) { - //check vcard hash - NSString* avatarHash = [presenceNode findFirst:@"{vcard-temp:x:update}x/photo#"]; - NSString* currentHash = [[DataLayer sharedInstance] getAvatarHashForContact:presenceNode.fromUser andAccount:_account.accountNo]; - DDLogVerbose(@"Checking if avatar hash in presence '%@' equals stored hash '%@'...", avatarHash, currentHash); - if(avatarHash != nil && !(currentHash && [avatarHash isEqualToString:currentHash])) - { - DDLogInfo(@"Got new muc avatar hash '%@' for muc %@, fetching new image via vcard-temp...", avatarHash, presenceNode.fromUser); - [self fetchAvatarForRoom:presenceNode.fromUser]; - } - else if(avatarHash == nil && currentHash != nil && ![currentHash isEqualToString:@""]) - { - [[MLImageManager sharedInstance] setIconForContact:[MLContact createContactFromJid:presenceNode.fromUser andAccountNo:_account.accountNo] WithData:nil]; - [[DataLayer sharedInstance] setAvatarHash:@"" forContact:presenceNode.fromUser andAccount:_account.accountNo]; - //delete cache to make sure the image will be regenerated - [[MLImageManager sharedInstance] purgeCacheForContact:presenceNode.fromUser andAccount:_account.accountNo]; - [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:_account userInfo:@{ - @"contact": [MLContact createContactFromJid:presenceNode.fromUser andAccountNo:_account.accountNo] - }]; - DDLogInfo(@"Avatar of muc '%@' deleted successfully", presenceNode.fromUser); - } - else - DDLogInfo(@"Avatar hash '%@' of muc %@ did not change, not updating avatar...", avatarHash, presenceNode.fromUser); + DDLogInfo(@"Got new muc avatar hash '%@' for muc %@, fetching new image via vcard-temp...", avatarHash, presenceNode.fromUser); + [self fetchAvatarForRoom:presenceNode.fromUser]; + } + else if(avatarHash == nil && currentHash != nil && ![currentHash isEqualToString:@""]) + { + [[MLImageManager sharedInstance] setIconForContact:[MLContact createContactFromJid:presenceNode.fromUser andAccountNo:_account.accountNo] WithData:nil]; + [[DataLayer sharedInstance] setAvatarHash:@"" forContact:presenceNode.fromUser andAccount:_account.accountNo]; + //delete cache to make sure the image will be regenerated + [[MLImageManager sharedInstance] purgeCacheForContact:presenceNode.fromUser andAccount:_account.accountNo]; + [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:_account userInfo:@{ + @"contact": [MLContact createContactFromJid:presenceNode.fromUser andAccountNo:_account.accountNo] + }]; + DDLogInfo(@"Avatar of muc '%@' deleted successfully", presenceNode.fromUser); } else - DDLogInfo(@"Ignoring presence updates from %@, MUC not in buddylist", presenceNode.fromUser); + DDLogInfo(@"Avatar hash '%@' of muc %@ did not change, not updating avatar...", avatarHash, presenceNode.fromUser); + } } //handle reflected presences else @@ -307,14 +304,9 @@ -(void) processPresence:(XMPPPresence*) presenceNode [[DataLayer sharedInstance] removeParticipant:item fromMuc:presenceNode.fromUser forAccountId:_account.accountNo]; else { - if([[DataLayer sharedInstance] isContactInList:presenceNode.fromUser forAccount:_account.accountNo]) - { - if(item[@"jid"] != nil) - [self handleMembersListUpdate:presenceNode]; - [[DataLayer sharedInstance] addParticipant:item toMuc:presenceNode.fromUser forAccountId:_account.accountNo]; - } - else - DDLogInfo(@"Ignoring presence updates from %@, MUC not in buddylist", presenceNode.fromUser); + if(item[@"jid"] != nil) + [self handleMembersListUpdate:presenceNode]; + [[DataLayer sharedInstance] addParticipant:item toMuc:presenceNode.fromUser forAccountId:_account.accountNo]; } //handle muc status codes in self-presences (after the above code, to make sure we are registered as participant in our db, too) @@ -376,7 +368,8 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode -(void) handleMembersListUpdate:(XMPPStanza*) node { - if([[DataLayer sharedInstance] isContactInList:node.fromUser forAccount:_account.accountNo]) + //check if this is still a muc and ignore the members list update, if not + if([[DataLayer sharedInstance] isBuddyMuc:node.fromUser forAccount:_account.accountNo]) { for(NSDictionary* entry in [node find:@"{http://jabber.org/protocol/muc#admin}query/item@@"]) { From 13b735fc6d55f7b4f60995bd8c1cdb36094b8a52 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:55:10 +0100 Subject: [PATCH 042/115] cleanup --- Monal/Classes/ChannelMemberList.swift | 28 +++++++-------- Monal/Classes/CreateGroupMenu.swift | 2 +- Monal/Classes/MemberList.swift | 51 ++++++++++++++------------- 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Monal/Classes/ChannelMemberList.swift b/Monal/Classes/ChannelMemberList.swift index d5f9c41878..8c9cd7d172 100644 --- a/Monal/Classes/ChannelMemberList.swift +++ b/Monal/Classes/ChannelMemberList.swift @@ -15,6 +15,20 @@ struct ChannelMemberList: View { @ObservedObject var channel: ObservableKVOWrapper private let account: xmpp? + init(channelContact: ObservableKVOWrapper) { + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: channelContact.accountId)! as xmpp + self.channel = channelContact; + + let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: channelContact.contactJid, forAccountId: channelContact.accountId)) + var nickSet : OrderedDictionary = OrderedDictionary() + for jidDict in jidList { + if let nick = jidDict["room_nick"] as? String { + nickSet.updateValue(jidDict["affiliation"]! as! String, forKey: nick) + } + } + _channelMembers = State(wrappedValue: nickSet) + } + var body: some View { List { Section(header: Text(self.channel.obj.contactDisplayName)) { @@ -38,20 +52,6 @@ struct ChannelMemberList: View { } .navigationBarTitle("Channel Members", displayMode: .inline) } - - init(channelContact: ObservableKVOWrapper) { - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: channelContact.obj.accountId)! as xmpp - self.channel = channelContact; - - let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: channelContact.obj.contactJid, forAccountId: channelContact.obj.accountId)) - var nickSet : OrderedDictionary = OrderedDictionary() - for jidDict in jidList { - if let nick = jidDict["room_nick"] as? String { - nickSet.updateValue(jidDict["affiliation"]! as! String, forKey: nick) - } - } - _channelMembers = State(wrappedValue: nickSet) - } } /*struct ChannelMemberList_Previews: PreviewProvider { diff --git a/Monal/Classes/CreateGroupMenu.swift b/Monal/Classes/CreateGroupMenu.swift index 4b69c477bc..5f1c567b36 100644 --- a/Monal/Classes/CreateGroupMenu.swift +++ b/Monal/Classes/CreateGroupMenu.swift @@ -54,7 +54,7 @@ struct CreateGroupMenu: View { Section() { Picker("Use account", selection: $selectedAccount) { ForEach(Array(self.connectedAccounts.enumerated()), id: \.element) { idx, account in - Text(account.connectionProperties.identity.jid).tag(account) + Text(account.connectionProperties.identity.jid).tag(account as xmpp?) } } .pickerStyle(.menu) diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index eb313e54fe..e5d11b10e5 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -22,6 +22,28 @@ struct MemberList: View { @State private var showAlert = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) + + init(mucContact: ObservableKVOWrapper?) { + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact!.accountId)! as xmpp + self.group = mucContact!; + _memberList = State(wrappedValue: getContactList(viewContact: mucContact)) + var affiliationTmp = Dictionary() + for memberInfo in Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact!.contactJid, forAccountId: self.account!.accountNo)) { + var jid : String? = memberInfo["participant_jid"] as? String + if(jid == nil) { + jid = memberInfo["member_jid"] as? String + } + if(jid == nil) { + continue + } + if(jid == self.account?.connectionProperties.identity.jid) { + self.ownAffiliation = memberInfo["affiliation"]! as! String + } + affiliationTmp.updateValue(memberInfo["affiliation"]! as! String, forKey: jid!) + } + _affiliation = State(wrappedValue: affiliationTmp) + } + func setAndShowAlert(title: String, description: String) { self.alertPrompt.title = Text(title) self.alertPrompt.message = Text(description) @@ -32,7 +54,7 @@ struct MemberList: View { if contact.obj.contactJid == self.account?.connectionProperties.identity.jid { return false } - if let contactAffiliation = self.affiliation[contact.obj.contactJid] { + if let contactAffiliation = self.affiliation[contact.contactJid] { if self.ownAffiliation == "owner" { return true } else if self.ownAffiliation == "admin" && contactAffiliation == "member" { @@ -52,12 +74,12 @@ struct MemberList: View { NavigationLink(destination: LazyClosureView(ContactDetails(delegate: SheetDismisserProtocol(), contact: contact)), label: { ZStack(alignment: .topLeading) { HStack(alignment: .center) { - Image(uiImage: contact.obj.avatar) + Image(uiImage: contact.avatar) .resizable() .frame(width: 40, height: 40, alignment: .center) Text(contact.contactDisplayName as String) Spacer() - if let contactAffiliation = self.affiliation[contact.obj.contactJid] { + if let contactAffiliation = self.affiliation[contact.contactJid] { if contactAffiliation == "owner" { Text(NSLocalizedString("Owner", comment: "")) } else if contactAffiliation == "admin" { @@ -75,7 +97,7 @@ struct MemberList: View { } .onDelete(perform: { memberIdx in let member = self.memberList[memberIdx.first!] - self.account!.mucProcessor.setAffiliation("none", ofUser: member.contactJid, inMuc: self.group.obj.contactJid) + self.account!.mucProcessor.setAffiliation("none", ofUser: member.contactJid, inMuc: self.group.contactJid) self.setAndShowAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) self.memberList.remove(at: memberIdx.first!) @@ -86,27 +108,6 @@ struct MemberList: View { } .navigationBarTitle("Group Members", displayMode: .inline) } - - init(mucContact: ObservableKVOWrapper?) { - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact!.obj.accountId)! as xmpp - self.group = mucContact!; - _memberList = State(wrappedValue: getContactList(viewContact: mucContact)) - var affiliationTmp = Dictionary() - for memberInfo in Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact!.contactJid, forAccountId: self.account!.accountNo)) { - var jid : String? = memberInfo["participant_jid"] as? String - if(jid == nil) { - jid = memberInfo["member_jid"] as? String - } - if(jid == nil) { - continue - } - if(jid == self.account?.connectionProperties.identity.jid) { - self.ownAffiliation = memberInfo["affiliation"]! as! String - } - affiliationTmp.updateValue(memberInfo["affiliation"]! as! String, forKey: jid!) - } - _affiliation = State(wrappedValue: affiliationTmp) - } } struct MemberList_Previews: PreviewProvider { From 2931e530c763c4f08a7246706b60f471c37ddca0 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:56:47 +0100 Subject: [PATCH 043/115] fix build --- Monal/Classes/MLMucProcessor.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 87ff29b0b4..b2f7526a79 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -281,6 +281,7 @@ -(void) processPresence:(XMPPPresence*) presenceNode DDLogInfo(@"Avatar of muc '%@' deleted successfully", presenceNode.fromUser); } else + { DDLogInfo(@"Avatar hash '%@' of muc %@ did not change, not updating avatar...", avatarHash, presenceNode.fromUser); } } From 551f24b228c39ee082dca4dc5b64b07b8cff69f3 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 19:05:12 +0100 Subject: [PATCH 044/115] Fix live updates of muc members --- Monal/Classes/MLMucProcessor.m | 38 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index b2f7526a79..8138581d63 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -300,17 +300,17 @@ -(void) processPresence:(XMPPPresence*) presenceNode item[@"jid"] = [HelperTools splitJid:item[@"jid"]][@"user"]; item[@"nick"] = presenceNode.fromResource; - //handle presences + //handle participant updates if([presenceNode check:@"/"]) [[DataLayer sharedInstance] removeParticipant:item fromMuc:presenceNode.fromUser forAccountId:_account.accountNo]; else - { - if(item[@"jid"] != nil) - [self handleMembersListUpdate:presenceNode]; [[DataLayer sharedInstance] addParticipant:item toMuc:presenceNode.fromUser forAccountId:_account.accountNo]; - } - //handle muc status codes in self-presences (after the above code, to make sure we are registered as participant in our db, too) + //handle members updates + if(item[@"jid"] != nil) + [self handleMembersListUpdate:[presenceNode find:@"{http://jabber.org/protocol/muc#user}x/item@@"] forMuc:presenceNode.fromUser]; + + //handle muc status codes in reflected presences (after the above code, to make sure we are registered as participant in our db, too) if([presenceNode check:@"/{jabber:client}presence/{http://jabber.org/protocol/muc#user}x/status@code"]) [self handleStatusCodes:presenceNode]; } @@ -320,7 +320,7 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode { //handle member list updates of offline members (useful for members-only mucs) if([messageNode check:@"{http://jabber.org/protocol/muc#user}x/item"]) - [self handleMembersListUpdate:messageNode]; + [self handleMembersListUpdate:[messageNode find:@"{http://jabber.org/protocol/muc#user}x/item@@"] forMuc:messageNode.fromUser]; //handle muc status codes [self handleStatusCodes:messageNode]; @@ -367,27 +367,32 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode return NO; } --(void) handleMembersListUpdate:(XMPPStanza*) node +-(void) handleMembersListUpdate:(NSArray*) items forMuc:(NSString*) mucJid; { //check if this is still a muc and ignore the members list update, if not - if([[DataLayer sharedInstance] isBuddyMuc:node.fromUser forAccount:_account.accountNo]) + if([[DataLayer sharedInstance] isBuddyMuc:mucJid forAccount:_account.accountNo]) { - for(NSDictionary* entry in [node find:@"{http://jabber.org/protocol/muc#admin}query/item@@"]) + DDLogInfo(@"Handling members list update for %@: %@", mucJid, items); + for(NSDictionary* entry in items) { NSMutableDictionary* item = [entry mutableCopy]; if(!item || item[@"jid"] == nil) + { + DDLogDebug(@"Ignoring empty item/jid: %@", item); continue; //ignore empty items or items without a jid + } //update jid to be a bare jid item[@"jid"] = [HelperTools splitJid:item[@"jid"]][@"user"]; #ifndef DISABLE_OMEMO - BOOL isTypeGroup = [[[DataLayer sharedInstance] getMucTypeOfRoom:node.fromUser andAccount:_account.accountNo] isEqualToString:@"group"]; + BOOL isTypeGroup = [[[DataLayer sharedInstance] getMucTypeOfRoom:mucJid andAccount:_account.accountNo] isEqualToString:@"group"]; #endif if([@"none" isEqualToString:item[@"affiliation"]]) { - [[DataLayer sharedInstance] removeMember:item fromMuc:node.fromUser forAccountId:_account.accountNo]; + DDLogVerbose(@"Removing member '%@' from muc '%@'...", item[@"jid"], mucJid); + [[DataLayer sharedInstance] removeMember:item fromMuc:mucJid forAccountId:_account.accountNo]; #ifndef DISABLE_OMEMO if(isTypeGroup == YES) [_account.omemo checkIfSessionIsStillNeeded:item[@"jid"] isMuc:NO]; @@ -395,7 +400,8 @@ -(void) handleMembersListUpdate:(XMPPStanza*) node } else { - [[DataLayer sharedInstance] addMember:item toMuc:node.fromUser forAccountId:_account.accountNo]; + DDLogVerbose(@"Adding member '%@' to muc '%@'...", item[@"jid"], mucJid); + [[DataLayer sharedInstance] addMember:item toMuc:mucJid forAccountId:_account.accountNo]; #ifndef DISABLE_OMEMO if(isTypeGroup == YES) [_account.omemo subscribeAndFetchDevicelistIfNoSessionExistsForJid:item[@"jid"]]; @@ -404,7 +410,7 @@ -(void) handleMembersListUpdate:(XMPPStanza*) node } } else - DDLogInfo(@"Ignoring handleMembersListUpdate for %@, MUC not in buddylist", node.fromUser); + DDLogWarn(@"Ignoring handleMembersListUpdate for %@, MUC not in buddylist", mucJid); } -(void) configureMuc:(NSString*) roomJid withMandatoryOptions:(NSDictionary*) mandatoryOptions andOptionalOptions:(NSDictionary*) optionalOptions deletingMucOnError:(BOOL) deleteOnError andJoiningMucOnSuccess:(BOOL) joinOnSuccess @@ -1037,7 +1043,7 @@ -(void) setAffiliation:(NSString*) affiliation ofUser:(NSString*) jid inMuc:(NSS [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to change affiliation of '%@' in '%@' to '%@'", @""), jid, roomJid, affiliation] forMuc:roomJid withNode:iqNode andIsSevere:YES]; return; } - DDLogError(@"Successfully changed affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); + DDLogInfo(@"Successfully changed affiliation of '%@' in '%@' to '%@'", jid, roomJid, affiliation); $$ -(void) changeNameOfMuc:(NSString*) room to:(NSString*) name @@ -1279,7 +1285,7 @@ -(void) sendJoinPresenceFor:(NSString*) room $$instance_handler(handleMembersList, account.mucProcessor, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, type)) DDLogInfo(@"Got %@s list from %@...", type, iqNode.fromUser); - [self handleMembersListUpdate:iqNode]; + [self handleMembersListUpdate:[iqNode find:@"{http://jabber.org/protocol/muc#admin}query/item@@"] forMuc:iqNode.fromUser]; [self logMembersOfMuc:iqNode.fromUser]; $$ From 4e3ddafda5b9f201c39cb1fba951c77a5b21218d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 20:34:28 +0100 Subject: [PATCH 045/115] Fix handling of muc removals --- Monal/Classes/MLMucProcessor.m | 45 +++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 8138581d63..ecf913a49f 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -310,7 +310,8 @@ -(void) processPresence:(XMPPPresence*) presenceNode if(item[@"jid"] != nil) [self handleMembersListUpdate:[presenceNode find:@"{http://jabber.org/protocol/muc#user}x/item@@"] forMuc:presenceNode.fromUser]; - //handle muc status codes in reflected presences (after the above code, to make sure we are registered as participant in our db, too) + //handle muc status codes in reflected presences + //this MUST be done after the above code to make sure the db correctly reflects our membership/participant status if([presenceNode check:@"/{jabber:client}presence/{http://jabber.org/protocol/muc#user}x/status@code"]) [self handleStatusCodes:presenceNode]; } @@ -543,6 +544,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node NSMutableSet* unhandledStatusCodes = [NSMutableSet new]; NSMutableSet* jointCodes = [presenceCodes mutableCopy]; [jointCodes unionSet:messageCodes]; + BOOL selfPrecenceHandled = NO; for(NSNumber* code in jointCodes) switch([code intValue]) { @@ -555,6 +557,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node //update nick in database DDLogInfo(@"Updating muc %@ nick in database to nick provided by server: '%@'...", node.fromUser, node.fromResource); [[DataLayer sharedInstance] updateOwnNickName:node.fromResource forMuc:node.fromUser forAccount:_account.accountNo]; + selfPrecenceHandled = YES; } break; } @@ -567,22 +570,33 @@ -(void) handleStatusCodes:(XMPPStanza*) node DDLogDebug(@"got banned from room %@", node.fromUser); [self removeRoomFromJoining:node.fromUser]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got banned from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + selfPrecenceHandled = YES; } break; } //kicked from room case 307: { - DDLogDebug(@"user '%@' got kicked from room %@", node.fromResource, node.fromUser); - if([nick isEqualToString:node.fromResource]) + /* + * To quote XEP-0045: + * Note: Some server implementations additionally include a 307 status code (signifying a 'kick', i.e. a forced ejection from the room). This is generally not advisable, as these types of disconnects may be frequent in the presence of poor network conditions and they are not linked to any user (e.g. moderator) action that the 307 code usually indicates. It is therefore recommended for the client to ignore the 307 code if a 333 status code is present. + */ + if(![jointCodes containsObject:@333]) { - DDLogDebug(@"got kicked from room %@", node.fromUser); - [self removeRoomFromJoining:node.fromUser]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got kicked from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + DDLogDebug(@"user '%@' got kicked from room %@", node.fromResource, node.fromUser); + if([nick isEqualToString:node.fromResource]) + { + DDLogDebug(@"got kicked from room %@", node.fromUser); + [self removeRoomFromJoining:node.fromUser]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got kicked from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + selfPrecenceHandled = YES; + } } + else + DDLogWarn(@"Ignoring 307 status code because code 333 is present, too..."); break; } - //removed because of affiliation change --> reenter room + //removed because of affiliation change case 321: { //only handle this and rejoin, if we did not get removed from a members-only room @@ -591,9 +605,16 @@ -(void) handleStatusCodes:(XMPPStanza*) node DDLogDebug(@"user '%@' got affiliation changed for room %@", node.fromResource, node.fromUser); if([nick isEqualToString:node.fromResource]) { - DDLogDebug(@"got affiliation change for room %@", node.fromUser); - [self removeRoomFromJoining:node.fromUser]; - [self sendDiscoQueryFor:node.fromUser withJoin:YES andBookmarksUpdate:YES]; + DDLogDebug(@"got own affiliation change for room %@", node.fromUser); + //check if we are still in the room (e.g. loss of membership status in public channel or admin to member degradation) + if([[DataLayer sharedInstance] getParticipantForNick:node.fromResource inRoom:node.fromUser forAccountId:_account.accountNo] == nil) + { + DDLogInfo(@"Lost membership..."); + [self removeRoomFromJoining:node.fromUser]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because muc is now members-only: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; + selfPrecenceHandled = YES; + } } } break; @@ -608,6 +629,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node [self removeRoomFromJoining:node.fromUser]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because muc is now members-only: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; + selfPrecenceHandled = YES; } break; } @@ -620,6 +642,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node DDLogDebug(@"got removed from room %@ because of system shutdown", node.fromUser); [self removeRoomFromJoining:node.fromUser]; [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because of system shutdown: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + selfPrecenceHandled = YES; } break; } @@ -666,7 +689,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node //this is a self-presence (marking the end of the presence flood if we are in joining state) //handle this code last because it may reset _joining - if([presenceCodes containsObject:@110]) + if([presenceCodes containsObject:@110] && !selfPrecenceHandled) { //check if we have joined already (we handle only non-joining self-presences here) //joining self-presences are handled below From 2c69911c6ab8eaea393038dbc748d7bd488b60c5 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 20:43:53 +0100 Subject: [PATCH 046/115] Fix last interaction time for jids not in our roster --- Monal/Classes/MLContact.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/MLContact.m b/Monal/Classes/MLContact.m index afc77b9a22..a841d17975 100644 --- a/Monal/Classes/MLContact.m +++ b/Monal/Classes/MLContact.m @@ -214,7 +214,7 @@ +(MLContact*) createContactFromJid:(NSString*) jid andAccountNo:(NSNumber*) acco @"state": @"offline", @"count": @0, @"isActiveChat": @NO, - @"lastInteraction": [[NSDate date] initWithTimeIntervalSince1970:0], + @"lastInteraction": nilWrapper(nil), }]; } else From c3b9193061b3f25725db5c927fb35524708afd6c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 22:03:03 +0100 Subject: [PATCH 047/115] Fix mediated invite handling --- Monal/Classes/MLMucProcessor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index ecf913a49f..e0ef2b6aa5 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -333,7 +333,7 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode if(![messageNode.toUser isEqualToString:_account.connectionProperties.identity.jid]) return YES; //stop processing in MLMessageProcessor and ignore this invite - MLContact* inviteFrom = [MLContact createContactFromJid:[messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"] andAccountNo:_account.accountNo]; + MLContact* inviteFrom = [MLContact createContactFromJid:[HelperTools splitJid:[messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"]][@"user"] andAccountNo:_account.accountNo]; DDLogInfo(@"Got mediated muc invite from %@ for %@...", inviteFrom, messageNode.fromUser); if(!inviteFrom.isSubscribedFrom) { From c038c53fbde6567d6aa7a8f5ec13cc9bfe2a63d6 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 22:45:29 +0100 Subject: [PATCH 048/115] Don't leave muc if already not in bookmarks anymore --- Monal/Classes/MLPubSubProcessor.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Monal/Classes/MLPubSubProcessor.m b/Monal/Classes/MLPubSubProcessor.m index a5671ad41b..714e76d7f9 100644 --- a/Monal/Classes/MLPubSubProcessor.m +++ b/Monal/Classes/MLPubSubProcessor.m @@ -268,10 +268,15 @@ @implementation MLPubSubProcessor for(NSString* itemId in data) { NSString* room = [itemId lowercaseString]; - DDLogInfo(@"Leaving muc '%@' on account %@ because not listed in bookmarks anymore...", room, account.accountNo); - //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + if([ownFavorites containsObject:room]) + { + DDLogInfo(@"Leaving muc '%@' on account %@ because not listed in bookmarks anymore...", room, account.accountNo); + //delete local favorites entry and leave room afterwards + [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; + [account.mucProcessor leave:room withBookmarksUpdate:NO]; + } + else + DDLogVerbose(@"Ignoring retracted bookmark because not listed in muc_favorites already..."); } } else From 3ea719abeb0ba7ddbe75493882f660cc84be5040 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 20 Feb 2024 22:55:30 +0100 Subject: [PATCH 049/115] Don't close muc active chat if only autojoin changed --- Monal/Classes/MLMucProcessor.h | 2 +- Monal/Classes/MLMucProcessor.m | 4 ++-- Monal/Classes/MLPubSubProcessor.m | 22 ++++++++-------------- Monal/Classes/xmpp.m | 2 +- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.h b/Monal/Classes/MLMucProcessor.h index f9283990f6..2e06ecf3d2 100644 --- a/Monal/Classes/MLMucProcessor.h +++ b/Monal/Classes/MLMucProcessor.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN -(BOOL) processMessage:(XMPPMessage*) messageNode; -(void) join:(NSString*) room; --(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks; +-(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks keepBuddylistEntry:(BOOL) keepBuddylistEntry; //muc management methods -(NSString* _Nullable) createGroup:(NSString* _Nullable) node; diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index e0ef2b6aa5..66fdb072c7 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -882,7 +882,7 @@ -(void) join:(NSString*) room [self sendDiscoQueryFor:room withJoin:YES andBookmarksUpdate:YES]; } --(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks +-(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks keepBuddylistEntry:(BOOL) keepBuddylistEntry { room = [room lowercaseString]; NSString* nick = [[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:_account.accountNo]; @@ -905,7 +905,7 @@ -(void) leave:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks [_account send:presence]; //delete muc from favorites table and update bookmarks if requested - [self deleteMuc:room withBookmarksUpdate:updateBookmarks keepBuddylistEntry:NO]; + [self deleteMuc:room withBookmarksUpdate:updateBookmarks keepBuddylistEntry:keepBuddylistEntry]; } -(void) sendDiscoQueryFor:(NSString*) roomJid withJoin:(BOOL) join andBookmarksUpdate:(BOOL) updateBookmarks diff --git a/Monal/Classes/MLPubSubProcessor.m b/Monal/Classes/MLPubSubProcessor.m index 714e76d7f9..3cbd5e7091 100644 --- a/Monal/Classes/MLPubSubProcessor.m +++ b/Monal/Classes/MLPubSubProcessor.m @@ -239,9 +239,8 @@ @implementation MLPubSubProcessor else if([ownFavorites containsObject:room] && ![autojoin boolValue]) { DDLogInfo(@"Leaving muc '%@' on account %@ because not listed as autojoin=true in bookmarks...", room, account.accountNo); - //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + //delete local favorites entry and leave room afterwards, but keep buddylist entry because only the autojoin flag changed + [account.mucProcessor leave:room withBookmarksUpdate:NO keepBuddylistEntry:YES]; } //check for nickname changes else if([ownFavorites containsObject:room] && nick != nil) @@ -272,8 +271,7 @@ @implementation MLPubSubProcessor { DDLogInfo(@"Leaving muc '%@' on account %@ because not listed in bookmarks anymore...", room, account.accountNo); //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + [account.mucProcessor leave:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; } else DDLogVerbose(@"Ignoring retracted bookmark because not listed in muc_favorites already..."); @@ -287,8 +285,7 @@ @implementation MLPubSubProcessor { DDLogInfo(@"Leaving muc '%@' on account %@ because all bookmarks got deleted...", room, account.accountNo); //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + [account.mucProcessor leave:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; } } $$ @@ -503,9 +500,8 @@ @implementation MLPubSubProcessor else if([ownFavorites containsObject:room] && ![autojoin boolValue]) { DDLogInfo(@"Leaving muc '%@' on account %@ because not listed as autojoin=true in bookmarks...", room, account.accountNo); - //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + //delete local favorites entry and leave room afterwards, but keep buddylist entry because only the autojoin flag changed + [account.mucProcessor leave:room withBookmarksUpdate:NO keepBuddylistEntry:YES]; } //check for nickname changes else if([ownFavorites containsObject:room] && nick != nil) @@ -534,8 +530,7 @@ @implementation MLPubSubProcessor { DDLogInfo(@"Leaving muc '%@' on account %@ because not listed in bookmarks anymore...", room, account.accountNo); //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + [account.mucProcessor leave:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; } return; //we only need the first pep item (there should be only one item in the first place) @@ -548,8 +543,7 @@ @implementation MLPubSubProcessor { DDLogInfo(@"Leaving muc '%@' on account %@ because all bookmarks got deleted...", room, account.accountNo); //delete local favorites entry and leave room afterwards - [[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo]; - [account.mucProcessor leave:room withBookmarksUpdate:NO]; + [account.mucProcessor leave:room withBookmarksUpdate:NO keepBuddylistEntry:NO]; } $$ diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index e9980d174d..45db904e21 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -4381,7 +4381,7 @@ -(void) joinMuc:(NSString* _Nonnull) room -(void) leaveMuc:(NSString* _Nonnull) room { - [self.mucProcessor leave:room withBookmarksUpdate:YES]; + [self.mucProcessor leave:room withBookmarksUpdate:YES keepBuddylistEntry:NO]; } -(void) checkJidType:(NSString*) jid withCompletion:(void (^)(NSString* type, NSString* _Nullable errorMessage)) completion From e69c35db2dcaca8afed0905fb1750661d37d0952 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 00:52:07 +0100 Subject: [PATCH 050/115] Fix KVO for augmented properties of MLContact --- Monal/Classes/MLContact.m | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Monal/Classes/MLContact.m b/Monal/Classes/MLContact.m index a841d17975..2343a25628 100644 --- a/Monal/Classes/MLContact.m +++ b/Monal/Classes/MLContact.m @@ -412,6 +412,11 @@ -(BOOL) isSelfChat return [self.contactJid isEqualToString:account.connectionProperties.identity.jid]; } ++(NSSet*) keyPathsForValuesAffectingIsSelfChat +{ + return [NSSet setWithObjects:@"contactJid", @"accountId", nil]; +} + -(BOOL) isInRoster { // mucs have a subscription of both (ensured by the datalayer) @@ -420,33 +425,63 @@ -(BOOL) isInRoster || [self.ask isEqualToString:kAskSubscribe]; } ++(NSSet*) keyPathsForValuesAffectingIsInRoster +{ + return [NSSet setWithObjects:@"subscription", @"ask", nil]; +} + -(BOOL) isSubscribedTo { return [self.subscription isEqualToString:kSubBoth] || [self.subscription isEqualToString:kSubTo]; } ++(NSSet*) keyPathsForValuesAffectingIsSubscribedTo +{ + return [NSSet setWithObjects:@"subscription", nil]; +} + -(BOOL) isSubscribedFrom { return [self.subscription isEqualToString:kSubBoth] || [self.subscription isEqualToString:kSubFrom]; } ++(NSSet*) keyPathsForValuesAffectingIsSubscribedFrom +{ + return [NSSet setWithObjects:@"subscription", nil]; +} + -(BOOL) isSubscribedBoth { return [self.subscription isEqualToString:kSubBoth]; } ++(NSSet*) keyPathsForValuesAffectingIsSubscribedBoth +{ + return [NSSet setWithObjects:@"subscription", nil]; +} + -(BOOL) hasIncomingContactRequest { return self.isGroup == NO && [[DataLayer sharedInstance] hasContactRequestForContact:self]; } ++(NSSet*) keyPathsForValuesAffectingHasIncomingContactRequest +{ + return [NSSet setWithObjects:@"isGroup", nil]; +} + -(BOOL) hasOutgoingContactRequest { return self.isGroup == NO && [self.ask isEqualToString:kAskSubscribe]; } ++(NSSet*) keyPathsForValuesAffectingHasOutgoingContactRequest +{ + return [NSSet setWithObjects:@"isGroup", @"ask", nil]; +} + // this will cache the unread count on first access -(NSInteger) unreadCount { From 3c734db00d341cb93812a6ad634b71fd278ef862 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 01:14:31 +0100 Subject: [PATCH 051/115] Fix handling of muc status codes on incoming moderator actions --- Monal/Classes/MLMucProcessor.m | 39 ++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 66fdb072c7..bcad1ea887 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -569,8 +569,9 @@ -(void) handleStatusCodes:(XMPPStanza*) node { DDLogDebug(@"got banned from room %@", node.fromUser); [self removeRoomFromJoining:node.fromUser]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got banned from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:NO]; selfPrecenceHandled = YES; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got banned from group/channel: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } break; } @@ -588,8 +589,9 @@ -(void) handleStatusCodes:(XMPPStanza*) node { DDLogDebug(@"got kicked from room %@", node.fromUser); [self removeRoomFromJoining:node.fromUser]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got kicked from: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:NO]; selfPrecenceHandled = YES; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got kicked from group/channel: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } } else @@ -609,11 +611,11 @@ -(void) handleStatusCodes:(XMPPStanza*) node //check if we are still in the room (e.g. loss of membership status in public channel or admin to member degradation) if([[DataLayer sharedInstance] getParticipantForNick:node.fromResource inRoom:node.fromUser forAccountId:_account.accountNo] == nil) { - DDLogInfo(@"Lost membership..."); + DDLogInfo(@"Got removed from room..."); [self removeRoomFromJoining:node.fromUser]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because muc is now members-only: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; selfPrecenceHandled = YES; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"You got removed from group/channel: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } } } @@ -627,7 +629,7 @@ -(void) handleStatusCodes:(XMPPStanza*) node { DDLogDebug(@"got removed from members-only room %@", node.fromUser); [self removeRoomFromJoining:node.fromUser]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because muc is now members-only: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because group/channel is now members-only: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; [self deleteMuc:node.fromUser withBookmarksUpdate:YES keepBuddylistEntry:YES]; selfPrecenceHandled = YES; } @@ -641,8 +643,8 @@ -(void) handleStatusCodes:(XMPPStanza*) node { DDLogDebug(@"got removed from room %@ because of system shutdown", node.fromUser); [self removeRoomFromJoining:node.fromUser]; - [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked, because of system shutdown: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; selfPrecenceHandled = YES; + [self handleError:[NSString stringWithFormat:NSLocalizedString(@"Kicked from group/channel, because of system shutdown: %@", @""), node.fromUser] forMuc:node.fromUser withNode:node andIsSevere:YES]; } break; } @@ -657,6 +659,11 @@ -(void) handleStatusCodes:(XMPPStanza*) node switch([code intValue]) { //room created and needs configuration now + case 100: + DDLogVerbose(@"This room is non-anonymous: everybody can see all jids..."); + break; + case 110: + break; //ignore self-presence status handled below case 201: { if(![presenceCodes containsObject:@110]) @@ -678,8 +685,6 @@ -(void) handleStatusCodes:(XMPPStanza*) node return; break; } - case 110: - break; //ignore self-presence status handled below default: //only log errors for status codes not already handled by our joint handling above if([unhandledStatusCodes containsObject:code]) @@ -724,6 +729,11 @@ -(void) handleStatusCodes:(XMPPStanza*) node [_noUpdateBookmarks removeObject:node.fromUser]; } + DDLogDebug(@"Updating muc contact..."); + [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:_account userInfo:@{ + @"contact": [MLContact createContactFromJid:node.fromUser andAccountNo:_account.accountNo] + }]; + [self logMembersOfMuc:node.fromUser]; //load members/admins/owners list (this has to be done *after* joining the muc to not get auth errors) @@ -1507,22 +1517,23 @@ -(void) deleteMuc:(NSString*) room withBookmarksUpdate:(BOOL) updateBookmarks ke { DDLogInfo(@"Deleting muc %@ on account %@...", room, _account); - //create contact object before deleting it to retain the muc specific settings - //(after deleting it from our db, the contact for this jid will look like an ordinary 1:1 contact) - MLContact* contact = [MLContact createContactFromJid:room andAccountNo:_account.accountNo]; - //delete muc from favorites table and update bookmarks if requested [[DataLayer sharedInstance] deleteMuc:room forAccountId:_account.accountNo]; if(updateBookmarks) [self updateBookmarks]; //update buddylist (e.g. contact list) if requested + MLContact* contact = [MLContact createContactFromJid:room andAccountNo:_account.accountNo]; + [contact removeShareInteractions]; if(keepBuddylistEntry) - ; //TODO: mark entry as destroyed to be displayed in the ui + { + [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:_account userInfo:@{ + @"contact": contact + }]; + } else { [[DataLayer sharedInstance] removeBuddy:room forAccount:_account.accountNo]; - [contact removeShareInteractions]; [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRemoved object:_account userInfo:@{ @"contact": contact }]; From 0afc699b00fbafc1033393b4b69ef7459df34fea Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 01:15:23 +0100 Subject: [PATCH 052/115] Fix sending of chatstates in muc and cleanup sending of displaymarkers --- Monal/Classes/XMPPMessage.m | 9 ++------- Monal/Classes/xmpp.m | 8 ++------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Monal/Classes/XMPPMessage.m b/Monal/Classes/XMPPMessage.m index 3cfa2150f6..d93fb7ed8a 100644 --- a/Monal/Classes/XMPPMessage.m +++ b/Monal/Classes/XMPPMessage.m @@ -35,18 +35,14 @@ -(XMPPMessage*) init -(XMPPMessage*) initWithType:(NSString*) type to:(NSString*) to { - self = [self init]; - self.attributes[@"type"] = type; + self = [self initWithType:type]; self.attributes[@"to"] = to; - return self; } -(XMPPMessage*) initToContact:(MLContact*) toContact { - self = [self init]; - self.attributes[@"to"] = toContact.contactJid; - + self = [self initWithType:(toContact.isGroup ? kMessageGroupChatType : kMessageChatType) to:toContact.contactJid]; return self; } @@ -54,7 +50,6 @@ -(XMPPMessage*) initWithType:(NSString*) type { self = [self init]; self.attributes[@"type"] = type; - return self; } diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 45db904e21..0d7250948f 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -3364,8 +3364,7 @@ -(void) sendChatState:(BOOL) isTyping toContact:(nonnull MLContact*) contact if(self.accountState < kStateBound) return; - XMPPMessage* messageNode = [XMPPMessage new]; - messageNode.attributes[@"to"] = contact.contactJid; + XMPPMessage* messageNode = [[XMPPMessage alloc] initToContact:contact]; [messageNode setNoStoreHint]; if(isTyping) [messageNode addChildNode:[[MLXMLNode alloc] initWithElement:@"composing" andNamespace:@"http://jabber.org/protocol/chatstates"]]; @@ -5284,10 +5283,7 @@ -(void) sendDisplayMarkerForMessage:(MLMessage*) msg return; } - XMPPMessage* displayedNode = [XMPPMessage new]; - //the message type is needed so that the store hint is accepted by the server - displayedNode.attributes[@"type"] = msg.isMuc ? @"groupchat" : @"chat"; - displayedNode.attributes[@"to"] = msg.inbound ? msg.buddyName : self.connectionProperties.identity.jid; + XMPPMessage* displayedNode = [[XMPPMessage alloc] initToContact:contact]; [displayedNode setDisplayed:msg.isMuc && msg.stanzaId != nil ? msg.stanzaId : msg.messageId]; [displayedNode setStoreHint]; DDLogVerbose(@"Sending display marker: %@", displayedNode); From 62e9271eebc0ded8a7a980864c76a3577677635c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 02:31:36 +0100 Subject: [PATCH 053/115] Fix backscrolling in selfchats --- Monal/Classes/MLMessageProcessor.m | 20 ++++++++++++++++---- Monal/Classes/chatViewController.m | 16 ++++++++++++---- Monal/Classes/xmpp.m | 13 +++---------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index b999090f14..2bd6340bb6 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -123,9 +123,10 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag return nil; } - if(![messageNode check:@"/"] && [[messageNode from] isEqualToString:account.connectionProperties.identity.fullJid] && [[messageNode toUser] isEqualToString:account.connectionProperties.identity.jid]) { - return nil; - } + //TODO firedrich: thy that?? +// if(![messageNode check:@"/"] && [[messageNode from] isEqualToString:account.connectionProperties.identity.fullJid] && [[messageNode toUser] isEqualToString:account.connectionProperties.identity.jid]) { +// return nil; +// } //handle incoming jmi calls (TODO: add entry to local history, once the UI for this is implemented) //only handle incoming propose messages if not older than 60 seconds @@ -294,7 +295,10 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //handle muc status changes or invites (this checks for the muc namespace itself) if([account.mucProcessor processMessage:messageNode]) + { + DDLogVerbose(@"Muc processor said we have to stop message processing here..."); return nil; //the muc processor said we have stop processing + } //add contact if possible (ignore groupchats or already existing contacts, or KeyTransportElements) DDLogInfo(@"Adding possibly unknown contact for %@ to local contactlist (not updating remote roster!), doing nothing if contact is already known...", possiblyUnknownContact); @@ -346,7 +350,10 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag DDLogInfo(@"Got MUC subject for %@: %@", messageNode.fromUser, subject); if(subject == nil || [subject isEqualToString:currentSubject]) + { + DDLogVerbose(@"Ignoring subject, nothing changed..."); return nil; + } DDLogVerbose(@"Updating subject in database: %@", subject); [[DataLayer sharedInstance] updateMucSubject:subject forAccount:account.accountNo andRoom:messageNode.fromUser]; @@ -356,12 +363,17 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag @"subject": subject, }]; } + else + DDLogVerbose(@"Ignoring muc subject: isMLhistory=YES..."); return nil; } //ignore all other groupchat messages coming from bare jid (e.g. not being a "normal" muc message nor a subject update handled above) if([messageNode check:@"/"] && !messageNode.fromResource) + { + DDLogVerbose(@"Ignoring groupchat message without resource (should be already handled above)..."); return nil; + } NSString* decrypted; if([messageNode check:@"{eu.siacs.conversations.axolotl}encrypted/header"]) @@ -384,7 +396,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } #ifdef IS_ALPHA - //thats the negation of our case from line 193 + //thats the negation of our case from line 375 //--> opportunistic omemo in alpha builds should use the fallback body instead of the EME error because the fallback body could be the cleartext message // (it could be a real omemo fallback, too, but there is no harm in using that instead of the EME message) if(!([messageNode check:@"{eu.siacs.conversations.axolotl}encrypted/header"] && isMLhistory && [messageNode check:@"body#"])) diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index efc9cb39df..97e9bb14da 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -2710,7 +2710,7 @@ -(void) loadOldMsgHistory:(id) sender { NSString* errorText = error; if(!error) - errorText = NSLocalizedString(@"All messages already present in local history!", @""); + errorText = NSLocalizedString(@"Unknown error!", @""); DDLogError(@"Got backscrolling mam error: %@", errorText); UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Could not fetch messages", @"") message:[NSString stringWithFormat:NSLocalizedString(@"Could not fetch (all) old messages for this chat from your server archive. Please try again later. %@", @""), errorText] preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { @@ -2720,11 +2720,19 @@ -(void) loadOldMsgHistory:(id) sender } else { - if([messages count] == 0) { + DDLogVerbose(@"Got backscrolling mam response: %lu", (unsigned long)[messages count]); + if([messages count] == 0) + { self.moreMessagesAvailable = NO; + + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Finished fetching messages", @"") message:NSLocalizedString(@"All messages fetched successfully, there are no more left on the server!", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alert dismissViewControllerAnimated:YES completion:nil]; + }]]; + [self presentViewController:alert animated:YES completion:nil]; } - DDLogVerbose(@"Got backscrolling mam response: %lu", (unsigned long)[messages count]); - [self insertOldMessages:[[messages reverseObjectEnumerator] allObjects]]; + else + [self insertOldMessages:[[messages reverseObjectEnumerator] allObjects]]; } //allow next mam fetch self.isLoadingMam = NO; diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 0d7250948f..4a5285236c 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -4258,6 +4258,7 @@ -(void) setMAMQueryMostRecentForContact:(MLContact*) contact before:(NSString*) [[DataLayer sharedInstance] createTransaction:^{ DDLogVerbose(@"Handling mam page entry[%u(%@).%u(%@)]): %@", pageNo, @([pageList count]), entryNo, @([page count]), data); MLMessage* msg = [MLMessageProcessor processMessage:data[@"messageNode"] andOuterMessage:data[@"outerMessageNode"] forAccount:self withHistoryId:historyId]; + DDLogVerbose(@"Got message processor result: %@", msg); //add successfully added messages to our display list //stanzas not transporting a body will be processed, too, but the message processor will return nil for these if(msg != nil) @@ -4283,16 +4284,8 @@ -(void) setMAMQueryMostRecentForContact:(MLContact*) contact before:(NSString*) })); if([historyIdList count] < retrievedBodies) DDLogWarn(@"Got %lu mam history messages already contained in history db, possibly ougoing messages that did not have a stanzaid yet!", (unsigned long)(retrievedBodies - [historyIdList count])); - if(![historyIdList count]) - { - //call completion with nil to signal an error, if we could not get any messages not yet in history db - completion(nil, nil); - } - else - { - //query db (again) for the real MLMessage to account for changes in history table by non-body metadata messages received after the body-message - completion([[DataLayer sharedInstance] messagesForHistoryIDs:historyIdList], nil); - } + //query db (again) for the real MLMessage to account for changes in history table by non-body metadata messages received after the body-message + completion([[DataLayer sharedInstance] messagesForHistoryIDs:historyIdList], nil); }; responseHandler = ^(XMPPIQ* response) { NSMutableArray* mamPage = [self getOrderedMamPageFor:[response findFirst:@"/@id"]]; From dc4273388a8890d1186f1d4f24b4dd5fd06a2891 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 03:31:44 +0100 Subject: [PATCH 054/115] Add "Outcast" affiliation to gui --- Monal/Classes/MemberList.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index e5d11b10e5..1f86e63fab 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -84,8 +84,12 @@ struct MemberList: View { Text(NSLocalizedString("Owner", comment: "")) } else if contactAffiliation == "admin" { Text(NSLocalizedString("Admin", comment: "")) - } else { + } else if contactAffiliation == "member" { Text(NSLocalizedString("Member", comment: "")) + } else if contactAffiliation == "outcast" { + Text(NSLocalizedString("Outcast", comment: "")) + } else { + Text(NSLocalizedString("", comment: "")) } } } From 90b933828856d748f7b212f321d961153130b8ed Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 03:33:06 +0100 Subject: [PATCH 055/115] Bump marketing version to 6.2.0 --- Monal/Monal.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index eb48776270..2ccb090e27 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -2630,7 +2630,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.0; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; @@ -2963,7 +2963,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.0; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; @@ -3114,7 +3114,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.0; ONLY_ACTIVE_ARCH = YES; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; @@ -3374,7 +3374,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.0; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; @@ -3698,7 +3698,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.0; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = IS_QUICKSY; @@ -4074,7 +4074,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 14.0; LLVM_LTO = NO; MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.0; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SUPPORTS_MACCATALYST = YES; From a3a1ff5ef678591c680189d1d00e85741e1c2cd4 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 21 Feb 2024 21:53:52 +0100 Subject: [PATCH 056/115] Proper fix for #941 without backscrolling bugs --- Monal/Classes/MLMessageProcessor.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index 2bd6340bb6..9db6ed26ad 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -123,10 +123,9 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag return nil; } - //TODO firedrich: thy that?? -// if(![messageNode check:@"/"] && [[messageNode from] isEqualToString:account.connectionProperties.identity.fullJid] && [[messageNode toUser] isEqualToString:account.connectionProperties.identity.jid]) { -// return nil; -// } + //ignore messages from our own device, see this github issue: https://github.com/monal-im/Monal/issues/941 + if(![messageNode check:@"/"] && !isMLhistory && [messageNode.from isEqualToString:account.connectionProperties.identity.fullJid] && [messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) + return nil; //handle incoming jmi calls (TODO: add entry to local history, once the UI for this is implemented) //only handle incoming propose messages if not older than 60 seconds From 2f88093296c002b018bb37019f11b7732dfb5413 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 22 Feb 2024 00:08:12 +0100 Subject: [PATCH 057/115] Fix LMC and retraction handling in muc --- Monal/Classes/DataLayer.h | 2 +- Monal/Classes/DataLayer.m | 27 ++++++++++++++++++++++----- Monal/Classes/MLMessageProcessor.m | 8 ++++++-- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Monal/Classes/DataLayer.h b/Monal/Classes/DataLayer.h index 3895f18b6f..3d3b93b48e 100644 --- a/Monal/Classes/DataLayer.h +++ b/Monal/Classes/DataLayer.h @@ -209,7 +209,7 @@ extern NSString* const kMessageTypeFiletransfer; -(void) deleteMessageHistory:(NSNumber *) messageNo; -(void) deleteMessageHistoryLocally:(NSNumber*) messageNo; -(void) updateMessageHistory:(NSNumber*) messageNo withText:(NSString*) newText; --(NSNumber* _Nullable) getHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from andAccount:(NSNumber*) accountNo; +-(NSNumber* _Nullable) getHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo; -(NSDate* _Nullable) returnTimestampForQuote:(NSNumber*) historyID; -(BOOL) checkLMCEligible:(NSNumber*) historyID encrypted:(BOOL) encrypted historyBaseID:(NSNumber* _Nullable) historyBaseID; diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index db92907896..8508c042c9 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -1281,7 +1281,7 @@ -(NSNumber*) addMessageToChatBuddy:(NSString*) buddyName withInboundDir:(BOOL) i } else { - DDLogError(@"Message(%@) %@ with stanzaid %@ already existing, ignoring history update", accountNo, messageid, stanzaid); + DDLogWarn(@"Message(%@) %@ with stanzaid %@ already existing, ignoring history update: %@", accountNo, messageid, stanzaid, message); return (NSNumber*)nil; } }]; @@ -1501,12 +1501,29 @@ -(void) updateMessageHistory:(NSNumber*) messageNo withText:(NSString*) newText }]; } --(NSNumber*) getHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from andAccount:(NSNumber*) accountNo +-(NSNumber*) getHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from actualFrom:(NSString* _Nullable) actualFrom participantJid:(NSString* _Nullable) participantJid andAccount:(NSNumber*) accountNo { return [self.db idReadTransaction:^{ - return [self.db executeScalar:@"SELECT M.message_history_id FROM message_history AS M INNER JOIN account AS A ON M.account_id=A.account_id WHERE messageid=? AND ((M.buddy_name=? AND M.inbound=1) OR ((A.username || '@' || A.domain)=? AND M.inbound=0)) AND M.account_id=?;" andArguments:@[messageid, from, from, accountNo]]; - }]; -} + return [self.db executeScalar:@"SELECT M.message_history_id FROM message_history AS M INNER JOIN account AS A ON M.account_id=A.account_id INNER JOIN buddylist AS B on M.buddy_name = B.buddy_name AND M.account_id = B.account_id WHERE messageid=? AND M.account_id=? AND (\ + (B.Muc=0 AND ((M.buddy_name=? AND M.inbound=1) OR ((A.username || '@' || A.domain)=? AND M.inbound=0))) OR \ + (\ + B.Muc=1 AND M.buddy_name=? AND M.actual_from=? AND (\ + M.participant_jid=? OR M.participant_jid IS NULL \ + ) AND ( \ + (M.actual_from=B.muc_nick AND M.inbound=0) OR \ + (M.actual_from!=B.muc_nick AND M.inbound=1) \ + ) \ + ) \ + );" andArguments:@[messageid, accountNo, from, from, from, nilWrapper(actualFrom), nilWrapper(participantJid)]]; + }]; +} + +/* +CF6DE818-6036-4B2E-A228-717303D1E9FF +bififufuva@conference.xmpp.eightysoft.de +bififufuva@conference.xmpp.eightysoft.de +43 +*/ -(NSDate* _Nullable) returnTimestampForQuote:(NSNumber*) historyID { diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index 9db6ed26ad..6346ba0b12 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -392,6 +392,8 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } else decrypted = [account.omemo decryptMessage:messageNode withMucParticipantJid:participantJid]; + + DDLogVerbose(@"Decrypted: %@", decrypted); } #ifdef IS_ALPHA @@ -425,7 +427,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag { NSString* originIdToRetract = [messageNode findFirst:@"{urn:xmpp:fasten:0}apply-to@id"]; //this checks if this message is from the same jid as the message it tries to retract for (e.g. inbound can only retract inbound and outbound only outbound) - NSNumber* historyIdToRetract = [[DataLayer sharedInstance] getHistoryIDForMessageId:originIdToRetract from:messageNode.fromUser andAccount:account.accountNo]; + NSNumber* historyIdToRetract = [[DataLayer sharedInstance] getHistoryIDForMessageId:originIdToRetract from:messageNode.fromUser actualFrom:actualFrom participantJid:participantJid andAccount:account.accountNo]; if(historyIdToRetract != nil) { @@ -529,8 +531,10 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag if([messageNode check:@"{urn:xmpp:message-correct:0}replace"]) { NSString* messageIdToReplace = [messageNode findFirst:@"{urn:xmpp:message-correct:0}replace@id"]; + DDLogVerbose(@"Message id to LMC-replace: %@", messageIdToReplace); //this checks if this message is from the same jid as the message it tries to do the LMC for (e.g. inbound can only correct inbound and outbound only outbound) - historyId = [[DataLayer sharedInstance] getHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser andAccount:account.accountNo]; + historyId = [[DataLayer sharedInstance] getHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser actualFrom:actualFrom participantJid:participantJid andAccount:account.accountNo]; + DDLogVerbose(@"History id to LMC-replace: %@", historyId); //now check if the LMC is allowed (we use historyIdToUse for MLhistory mam queries to only check LMC for the 3 messages coming before this ID in this converastion) //historyIdToUse will be nil, for messages going forward in time which means (check for the newest 3 messages in this conversation) if(historyId != nil && [[DataLayer sharedInstance] checkLMCEligible:historyId encrypted:encrypted historyBaseID:historyIdToUse]) From 8996b6e2b47420ebeb4783d093d17510fa732905 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:15:29 +0100 Subject: [PATCH 058/115] Improve accessibility of chats button --- Monal/Classes/ActiveChatsViewController.h | 1 + Monal/Classes/ActiveChatsViewController.m | 23 +- Monal/Classes/HelperTools.h | 1 + Monal/localization/Base.lproj/Main.storyboard | 227 +++++++++--------- 4 files changed, 129 insertions(+), 123 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.h b/Monal/Classes/ActiveChatsViewController.h index 5901631ee1..0b3eb189e3 100644 --- a/Monal/Classes/ActiveChatsViewController.h +++ b/Monal/Classes/ActiveChatsViewController.h @@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) UITableView* chatListTable; @property (nonatomic, weak) IBOutlet UIBarButtonItem* settingsButton; +@property (weak, nonatomic) IBOutlet UIBarButtonItem* spinnerButton; @property (nonatomic, weak) IBOutlet UIBarButtonItem* composeButton; @property (nonatomic, strong) chatViewController* currentChatViewController; @property (nonatomic, strong) UIActivityIndicatorView* spinner; diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index e633ee2572..c3292d6152 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -70,14 +70,17 @@ -(id) initWithNibName:(NSString*) nibNameOrNil bundle:(NSBundle*) nibBundleOrNil -(void) configureComposeButton { - UIImage* composeImage = [[UIImage systemImageNamed:@"person.2.fill"] imageWithTintColor:UIColor.monalGreen]; - UITapGestureRecognizer* composeTapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showContacts:)]; - self.composeButton.customView = [HelperTools - buttonWithNotificationBadgeForImage:composeImage - hasNotification:[[DataLayer sharedInstance] allContactRequests].count > 0 - withTapHandler:composeTapRecoginzer]; - [self.composeButton.customView setIsAccessibilityElement:YES]; - [self.composeButton.customView setAccessibilityLabel:@"Open contacts list"]; + UIImage* composeImage = [[UIImage systemImageNamed:@"person.2.fill"] imageWithTintColor:UIColor.monalGreen]; + if([[DataLayer sharedInstance] allContactRequests].count > 0) + { + self.composeButton.image = [HelperTools imageWithNotificationBadgeForImage:composeImage]; + } + else + { + self.composeButton.image = composeImage; + } + [self.composeButton setAccessibilityLabel:@"Open contacts list"]; + [self.composeButton setAccessibilityHint:NSLocalizedString(@"Open contact list", @"")]; } -(void) viewDidLoad @@ -120,9 +123,7 @@ -(void) viewDidLoad self.settingsButton.image = [UIImage systemImageNamed:@"gearshape.fill"]; [self configureComposeButton]; - UIBarButtonItem* spinnerButton = [[UIBarButtonItem alloc] initWithCustomView:self.spinner]; - [spinnerButton setIsAccessibilityElement:NO]; - [self.navigationItem setRightBarButtonItems:@[self.composeButton, spinnerButton] animated:NO]; + self.spinnerButton.customView = self.spinner; self.chatListTable.emptyDataSetSource = self; self.chatListTable.emptyDataSetDelegate = self; diff --git a/Monal/Classes/HelperTools.h b/Monal/Classes/HelperTools.h index af470c9894..d263b0f84f 100644 --- a/Monal/Classes/HelperTools.h +++ b/Monal/Classes/HelperTools.h @@ -87,6 +87,7 @@ void swizzle(Class c, SEL orig, SEL new); +(void) addUploadItemPreviewForItem:(NSURL* _Nullable) url provider:(NSItemProvider* _Nullable) provider andPayload:(NSMutableDictionary*) payload withCompletionHandler:(void(^)(NSMutableDictionary* _Nullable)) completion; +(void) handleUploadItemProvider:(NSItemProvider*) provider withCompletionHandler:(void (^)(NSMutableDictionary* _Nullable)) completion; +(UIView*) buttonWithNotificationBadgeForImage:(UIImage*) image hasNotification:(bool) hasNotification withTapHandler: (UITapGestureRecognizer*) handler; ++(UIImage*) imageWithNotificationBadgeForImage:(UIImage*) image; +(NSData*) resizeAvatarImage:(UIImage* _Nullable) image withCircularMask:(BOOL) circularMask toMaxBase64Size:(unsigned long) length; +(double) report_memory; +(UIColor*) generateColorFromJid:(NSString*) jid; diff --git a/Monal/localization/Base.lproj/Main.storyboard b/Monal/localization/Base.lproj/Main.storyboard index 91c3b4ece8..9e80f78eaa 100644 --- a/Monal/localization/Base.lproj/Main.storyboard +++ b/Monal/localization/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -28,7 +28,7 @@ - + @@ -81,11 +81,11 @@ - + - + @@ -112,7 +112,7 @@ - + @@ -152,17 +152,21 @@ - - - - - - - + + + + + + + + + + + @@ -209,15 +213,15 @@ - + - + - + @@ -225,14 +229,14 @@ - + - + - + - + @@ -327,7 +331,7 @@ - + - + - + @@ -489,10 +493,10 @@ - + - + @@ -503,7 +507,7 @@ - + - + @@ -615,7 +619,7 @@ - + - + @@ -724,7 +728,7 @@ - + - + + + + + - - - + - - - + - - + + @@ -1990,17 +1994,16 @@ - - + - + - + - + - + @@ -2551,25 +2554,25 @@ - + - + - + - + - + From 50de9cbbb1c6fcad0c8755db4364a2fa804c72e4 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:59:01 +0100 Subject: [PATCH 059/115] Improve open contacts accessibility --- Monal/Classes/chatViewController.h | 7 ++++--- Monal/Classes/chatViewController.m | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Monal/Classes/chatViewController.h b/Monal/Classes/chatViewController.h index 418a80c6a8..1fcf7b497e 100644 --- a/Monal/Classes/chatViewController.h +++ b/Monal/Classes/chatViewController.h @@ -41,11 +41,12 @@ @property (weak, nonatomic) IBOutlet UICollectionView* uploadMenuView; @property (nonatomic, weak) IBOutlet UIView* inputContainerView; -@property (nonatomic, strong) IBOutlet UIView* navBarContainerView; @property (nonatomic, weak) IBOutlet NSLayoutConstraint* tableviewBottom; -@property (nonatomic, strong) IBOutlet UILabel* navBarContactJid; -@property (nonatomic, strong) IBOutlet UILabel* navBarLastInteraction; +@property (nonatomic, strong) UILabel* navBarContactJid; +@property (nonatomic, strong) UILabel* navBarLastInteraction; @property (nonatomic, strong) IBOutlet UIImageView* navBarIcon; +@property (nonatomic, strong) UIBarButtonItem* customHeader; + @property (weak, nonatomic) IBOutlet UIBarButtonItem* navBarEncryptToggleButton; @property (nonatomic, weak) IBOutlet UIImageView* backgroundImage; diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 97e9bb14da..705ea21451 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -293,15 +293,27 @@ -(void) initNavigationBarItems self.navBarContactJid = [[UILabel alloc] initWithFrame:CGRectMake(38, 7, 200, 18)]; self.navBarLastInteraction = [[UILabel alloc] initWithFrame:CGRectMake(38, 26, 200, 12)]; - [self.navBarContactJid setFont:[UIFont systemFontOfSize:15.0]]; - [self.navBarLastInteraction setFont:[UIFont systemFontOfSize:10.0]]; + self.navBarContactJid.font = [UIFont systemFontOfSize:15.0]; + self.navBarLastInteraction.font = [UIFont systemFontOfSize:10.0]; [cusView addSubview:self.navBarIcon]; [cusView addSubview:self.navBarContactJid]; [cusView addSubview:self.navBarLastInteraction]; - UITapGestureRecognizer* customViewTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(commandIPressed:)]; - [cusView addGestureRecognizer:customViewTapRecognizer]; - self.navigationItem.leftBarButtonItems = @[[[UIBarButtonItem alloc] initWithCustomView:cusView]]; + + UITapGestureRecognizer* openContactDetailsTapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(commandIPressed:)]; + [cusView addGestureRecognizer:openContactDetailsTapAction]; + + UIBarButtonItem* customViewButtonWithMultipleItems = [[UIBarButtonItem alloc] initWithCustomView:cusView]; + [customViewButtonWithMultipleItems setAction:@selector(commandIPressed:)]; + + // allow opening of contact details via voice over + [customViewButtonWithMultipleItems setIsAccessibilityElement:YES]; + [customViewButtonWithMultipleItems setAccessibilityTraits:UIAccessibilityTraitAllowsDirectInteraction]; + [customViewButtonWithMultipleItems setAccessibilityLabel:self.navBarContactJid.text]; + + self.customHeader = customViewButtonWithMultipleItems; + + self.navigationItem.leftBarButtonItems = @[customViewButtonWithMultipleItems]; self.navigationItem.leftItemsSupplementBackButton = YES; } @@ -630,9 +642,10 @@ -(void) updateUIElements // change text values dispatch_async(dispatch_get_main_queue(), ^{ self.navBarContactJid.text = jidLabelText; + [self.customHeader setAccessibilityLabel:jidLabelText]; self.sendButton.enabled = sendButtonEnabled; [[MLImageManager sharedInstance] getIconForContact:self.contact withCompletion:^(UIImage *image) { - self.navBarIcon.image=image; + self.navBarIcon.image = image; }]; [self updateCallButtonImage]; From 3d551ce2da3545339f15f800cee60d4d39db526f Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Wed, 21 Feb 2024 17:09:05 -0500 Subject: [PATCH 060/115] Improved accessibility for ContactDetails --- Monal/Classes/ContactDetails.swift | 58 +++++++++++-------- Monal/Classes/ContactDetailsHeader.swift | 71 ++++++++++++------------ 2 files changed, 70 insertions(+), 59 deletions(-) diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index f49149e585..eb52642566 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -30,7 +30,7 @@ struct ContactDetails: View { // info/nondestructive buttons Section { - Button(action: { + Button { if(contact.isGroup) { if(!contact.isMuted && !contact.isMentionOnly) { contact.obj.toggleMentionOnly(true) @@ -44,45 +44,52 @@ struct ContactDetails: View { } else { contact.obj.toggleMute(!contact.isMuted) } - }) { - HStack { - if(contact.isMuted) { + } label: { + if(contact.isMuted) { + Label { + contact.isGroup ? Text("Notifications disabled") : Text("Contact is muted") + } icon: { Image(systemName: "bell.slash.fill") .foregroundColor(.red) - contact.isGroup ? Text("Notifications disabled") : Text("Contact is muted") - } else if(contact.isGroup && contact.isMentionOnly) { - Image(systemName: "bell.badge") - .foregroundColor(.accentColor) + } + } else if(contact.isGroup && contact.isMentionOnly) { + Label { Text("Notify only when mentioned") - } else { + } icon: { + Image(systemName: "bell.badge") + } + } else { + Label { + contact.isGroup ? Text("Notify on all messages") : Text("Contact is not muted") + } icon: { Image(systemName: "bell.fill") .foregroundColor(.green) - contact.isGroup ? Text("Notify on all messages") : Text("Contact is not muted") } } } - //.buttonStyle(BorderlessButtonStyle()) #if !DISABLE_OMEMO if((!contact.isGroup || (contact.isGroup && contact.mucType == "group")) && !HelperTools.isContactBlacklisted(forEncryption:contact.obj)) { - Button(action: { + Button { if(contact.isEncrypted) { showingShouldDisableEncryptionAlert = true } else { showingCannotEncryptAlert = !contact.obj.toggleEncryption(!contact.isEncrypted) } - }) { - HStack { - if contact.isEncrypted { + } label: { + if contact.isEncrypted { + Label { + Text("Messages are encrypted") + } icon: { Image(systemName: "lock.fill") .foregroundColor(.green) - Text("Messages are encrypted") - .foregroundColor(.accentColor) - } else { + } + } else { + Label { + Text("Messages are NOT encrypted") + } icon: { Image(systemName: "lock.open.fill") .foregroundColor(.red) - Text("Messages are NOT encrypted") - .foregroundColor(.accentColor) } } } @@ -117,9 +124,14 @@ struct ContactDetails: View { .addClearButton(text:$contact.nickNameView) } - Button(contact.isPinned ? "Unpin Chat" : "Pin Chat") { - contact.obj.togglePinnedChat(!contact.isPinned); - } + Toggle("Pin Chat", isOn: Binding(get: { + contact.isPinned + }, set: { + contact.obj.togglePinnedChat($0) + })) +// Button(contact.isPinned ? "Unpin Chat" : "Pin Chat") { +// contact.obj.togglePinnedChat(!contact.isPinned); +// } if(contact.obj.isGroup && contact.obj.mucType == "group") { NavigationLink(destination: LazyClosureView(MemberList(mucContact: contact))) { diff --git a/Monal/Classes/ContactDetailsHeader.swift b/Monal/Classes/ContactDetailsHeader.swift index c657df9b09..e8676bd51d 100644 --- a/Monal/Classes/ContactDetailsHeader.swift +++ b/Monal/Classes/ContactDetailsHeader.swift @@ -17,40 +17,37 @@ struct ContactDetailsHeader: View { @State private var navigationAction: String? var body: some View { - VStack(alignment: .center) { - HStack { - Spacer() - Image(uiImage: contact.avatar) - .resizable() - .frame(width: 150, height: 150, alignment: .center) - .scaledToFit() - .shadow(radius: 7) - Spacer() - } + VStack(spacing: 20) { + Image(uiImage: contact.avatar) + .resizable() + .scaledToFit() + .accessibilityLabel("Avatar") + .frame(width: 150, height: 150, alignment: .center) + .shadow(radius: 7) + - Spacer() - .frame(height: 20) - HStack { - Text(contact.contactJid as String) - //for ios >= 15.0 - //.textSelection(.enabled) - Spacer().frame(width: 10) - Button(action: { - UIPasteboard.general.setValue(contact.contactJid as String, forPasteboardType:UTType.utf8PlainText.identifier as String) - }) { + Button { + UIPasteboard.general.setValue(contact.contactJid as String, forPasteboardType:UTType.utf8PlainText.identifier as String) + UIAccessibility.post(notification: .announcement, argument: "JID Copied") + } label: { + HStack { + Text(contact.contactJid as String) + Image(systemName: "doc.on.doc") .foregroundColor(.primary) + .accessibilityHidden(true) } - .buttonStyle(BorderlessButtonStyle()) + .accessibilityHint("Copies JID") } + .buttonStyle(.borderless) + + //only show account jid if more than one is configured if MLXMPPManager.sharedInstance().connectedXMPP.count > 1 && !contact.isSelfChat { Text("Account: \(MLXMPPManager.sharedInstance().getConnectedAccount(forID:contact.accountId)!.connectionProperties.identity.jid)") } if !contact.isSelfChat && !contact.isGroup { - Spacer() - .frame(height: 20) if let lastInteractionTime = contact.lastInteractionTime as Date? { if lastInteractionTime.timeIntervalSince1970 > 0 { Text(String(format: NSLocalizedString("Last seen: %@", comment: ""), @@ -64,27 +61,29 @@ struct ContactDetailsHeader: View { } if(!contact.isGroup && (contact.statusMessage as String).count > 0) { - Spacer() - .frame(height: 20) - Text("Status message:") - Text(contact.statusMessage as String) - .fixedSize(horizontal: false, vertical: true) + VStack { + Text("Status message:") + Text(contact.statusMessage as String) + .fixedSize(horizontal: false, vertical: true) + } } if(contact.isGroup && (contact.groupSubject as String).count > 0) { - Spacer() - .frame(height: 20) - if(contact.obj.mucType == "group") { - Text("Group subject:") - } else { - Text("Channel subject:") + VStack { + if(contact.obj.mucType == "group") { + Text("Group subject:") + } else { + Text("Channel subject:") + } + + Text(contact.groupSubject as String) + .fixedSize(horizontal: false, vertical: true) } - Text(contact.groupSubject as String) - .fixedSize(horizontal: false, vertical: true) } } .foregroundColor(.primary) .padding([.top, .bottom]) + .frame(maxWidth: .infinity) } } From bc7da2da4aae8a7f2a382c661d6c34ac58ecad7e Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Wed, 21 Feb 2024 17:11:41 -0500 Subject: [PATCH 061/115] Replaced conditional view modifiers with a more functional and safer ifAvailable view modifier that can be used when adding features only available in later OS versions. --- Monal/Classes/SwiftuiHelpers.swift | 38 +++++------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/Monal/Classes/SwiftuiHelpers.swift b/Monal/Classes/SwiftuiHelpers.swift index a1a1e7e434..2d08267bb1 100644 --- a/Monal/Classes/SwiftuiHelpers.swift +++ b/Monal/Classes/SwiftuiHelpers.swift @@ -319,42 +319,16 @@ struct AlertPrompt { var dismissLabel: Text = Text("Close") } -//see https://www.avanderlee.com/swiftui/conditional-view-modifier/ extension View { - /// Applies the given transform if the given condition evaluates to `true`. + /// Applies the given transform. + /// + /// Useful for availability branching on view modifiers. Do not branch with any properties that may change during runtime as this will cause errors. /// - Parameters: - /// - condition: The condition to evaluate. /// - transform: The transform to apply to the source `View`. - /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. - @ViewBuilder func `if`(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View { - if condition() { - transform(self) - } else { - self - } - } - - @ViewBuilder func `if`(closure condition: () -> Bool, transform: (Self) -> Content) -> some View { - if condition() { - transform(self) - } else { - self - } - } -} - -func iOS15() -> Bool { - guard #available(iOS 15, *) else { - return true - } - return false -} - -func iOS16() -> Bool { - guard #available(iOS 16, *) else { - return true + /// - Returns: The view transformed by the transform. + func ifAvailable(@ViewBuilder _ transform: (Self) -> Content) -> some View { + transform(self) } - return false } // Interfaces between ObjectiveC/Storyboards and SwiftUI From 74d5e7f10c7b39da8a4192cd32e94423b13148a6 Mon Sep 17 00:00:00 2001 From: Ryan Lintott Date: Wed, 21 Feb 2024 17:14:56 -0500 Subject: [PATCH 062/115] Improved the clear button for TextField. It's now placed next to the text field only when it's being edited and there is text visible. The icon was also changed to the standard xmark. --- Monal/Classes/AddContactMenu.swift | 7 +++++-- Monal/Classes/ContactDetails.swift | 9 ++++++--- Monal/Classes/CreateGroupMenu.swift | 17 ++++++++++------- Monal/Classes/PasswordMigration.swift | 2 +- Monal/Classes/SwiftuiHelpers.swift | 23 +++++++++++++++-------- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/Monal/Classes/AddContactMenu.swift b/Monal/Classes/AddContactMenu.swift index b597e2ac2e..a88b08aa8d 100644 --- a/Monal/Classes/AddContactMenu.swift +++ b/Monal/Classes/AddContactMenu.swift @@ -31,6 +31,8 @@ struct AddContactMenu: View { @State private var success = false @State private var newContact : MLContact? + @State private var isEditingJid = false + private let dismissWithNewContact: (MLContact) -> () private let preauthToken: String? @@ -181,12 +183,13 @@ struct AddContactMenu: View { } .pickerStyle(.menu) } - TextField(NSLocalizedString("Contact or Group/Channel Jid", comment: "placeholder when adding jid"), text: $toAdd) + + TextField(NSLocalizedString("Contact or Group/Channel Jid", comment: "placeholder when adding jid"), text: $toAdd, onEditingChanged: { isEditingJid = $0 }) //ios15: .textInputAutocapitalization(.never) .autocapitalization(.none) .autocorrectionDisabled() .keyboardType(.emailAddress) - .addClearButton(text:$toAdd) + .addClearButton(isEditing: isEditingJid, text:$toAdd) .disabled(scannedFingerprints != nil) .foregroundColor(scannedFingerprints != nil ? .secondary : .primary) .onChange(of: toAdd) { _ in diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index eb52642566..03db2e965a 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -21,6 +21,7 @@ struct ContactDetails: View { @State private var showingResetOmemoSessionConfirmation = false @State private var showingCannotEncryptAlert = false @State private var showingShouldDisableEncryptionAlert = false + @State private var isEditingNickname = false var body: some View { Form { @@ -119,9 +120,11 @@ struct ContactDetails: View { #endif if(!contact.isGroup && !contact.isSelfChat) { - TextField(NSLocalizedString("Rename Contact", comment: "placeholder text in contact details"), text: $contact.nickNameView) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .addClearButton(text:$contact.nickNameView) + TextField(NSLocalizedString("Rename Contact", comment: "placeholder text in contact details"), text: $contact.nickNameView, onEditingChanged: { + isEditingNickname = $0 + }) + .accessibilityLabel("Nickname") + .addClearButton(isEditing: isEditingNickname, text: $contact.nickNameView) } Toggle("Pin Chat", isOn: Binding(get: { diff --git a/Monal/Classes/CreateGroupMenu.swift b/Monal/Classes/CreateGroupMenu.swift index 5f1c567b36..0bbb79a4a2 100644 --- a/Monal/Classes/CreateGroupMenu.swift +++ b/Monal/Classes/CreateGroupMenu.swift @@ -23,6 +23,8 @@ struct CreateGroupMenu: View { // note: dismissLabel is not accessed but defined at the .alert() section @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) @State private var selectedContacts : OrderedSet = [] + + @State private var isEditingGroupName = false @ObservedObject private var overlay = LoadingOverlayState() @@ -52,16 +54,17 @@ struct CreateGroupMenu: View { else { Section() { - Picker("Use account", selection: $selectedAccount) { - ForEach(Array(self.connectedAccounts.enumerated()), id: \.element) { idx, account in - Text(account.connectionProperties.identity.jid).tag(account as xmpp?) - } + Picker("Use account", selection: $selectedAccount) { + ForEach(Array(self.connectedAccounts.enumerated()), id: \.element) { idx, account in + Text(account.connectionProperties.identity.jid).tag(account as xmpp?) } - .pickerStyle(.menu) - TextField(NSLocalizedString("Group Name (optional)", comment: "placeholder when creating new group"), text: $groupName) + } + .pickerStyle(.menu) + + TextField(NSLocalizedString("Group Name (optional)", comment: "placeholder when creating new group"), text: $groupName, onEditingChanged: { isEditingGroupName = $0 }) .autocorrectionDisabled() .autocapitalization(.none) - .addClearButton(text:$groupName) + .addClearButton(isEditing: isEditingGroupName, text:$groupName) NavigationLink(destination: LazyClosureView(ContactPicker(account: self.selectedAccount!, selectedContacts: $selectedContacts)), label: { Text("Change Group Members") diff --git a/Monal/Classes/PasswordMigration.swift b/Monal/Classes/PasswordMigration.swift index f090ca344c..83f7819225 100644 --- a/Monal/Classes/PasswordMigration.swift +++ b/Monal/Classes/PasswordMigration.swift @@ -78,7 +78,7 @@ struct PasswordMigration: View { } } )) - .addClearButton(text:Binding( + .addClearButton(isEditing: true, text:Binding( get: { self.needingMigration[id]?["password"] as? String ?? "" }, set: { self.needingMigration[id]?["password"] = $0 as NSString } )) diff --git a/Monal/Classes/SwiftuiHelpers.swift b/Monal/Classes/SwiftuiHelpers.swift index 2d08267bb1..a69d827bfd 100644 --- a/Monal/Classes/SwiftuiHelpers.swift +++ b/Monal/Classes/SwiftuiHelpers.swift @@ -158,26 +158,33 @@ extension DocumentPickerViewController: UIDocumentPickerDelegate { // clear button for text fields, see https://stackoverflow.com/a/58896723/3528174 struct ClearButton: ViewModifier { + let isEditing: Bool @Binding var text: String + public func body(content: Content) -> some View { - ZStack(alignment: .trailing) { + HStack { content - if(!text.isEmpty) { - Button(action: { + .accessibilitySortPriority(2) + + if isEditing, !text.isEmpty { + Button { self.text = "" - }) { - Image(systemName: "delete.left") - .foregroundColor(Color(UIColor.opaqueSeparator)) + } label: { + Image(systemName: "xmark.circle.fill") + .foregroundColor(Color(UIColor.tertiaryLabel)) + .accessibilityLabel("Clear text") } .padding(.trailing, 8) + .accessibilitySortPriority(1) } } } } //this extension contains the easy-access view modifier extension View { - func addClearButton(text: Binding) -> some View { - modifier(ClearButton(text:text)) + /// Puts the view in an HStack and adds a clear button to the right when the text is not empty. + func addClearButton(isEditing: Bool, text: Binding) -> some View { + modifier(ClearButton(isEditing: isEditing, text:text)) } } From fa3051726741a3ef43b1ea7ed67affeaca3e2dad Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:59:15 +0100 Subject: [PATCH 063/115] add clear buttons --- Monal/Classes/AddContactMenu.swift | 6 +++--- Monal/Classes/ContactPicker.swift | 5 ++++- Monal/Classes/WelcomeLogIn.swift | 7 ++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Monal/Classes/AddContactMenu.swift b/Monal/Classes/AddContactMenu.swift index a88b08aa8d..7bf3a6c913 100644 --- a/Monal/Classes/AddContactMenu.swift +++ b/Monal/Classes/AddContactMenu.swift @@ -30,9 +30,9 @@ struct AddContactMenu: View { @State private var showQRCodeScanner = false @State private var success = false @State private var newContact : MLContact? - + @State private var isEditingJid = false - + private let dismissWithNewContact: (MLContact) -> () private let preauthToken: String? @@ -183,7 +183,7 @@ struct AddContactMenu: View { } .pickerStyle(.menu) } - + TextField(NSLocalizedString("Contact or Group/Channel Jid", comment: "placeholder when adding jid"), text: $toAdd, onEditingChanged: { isEditingJid = $0 }) //ios15: .textInputAutocapitalization(.never) .autocapitalization(.none) diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index 18dc629085..2f61e6e06e 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -42,6 +42,8 @@ struct ContactPicker: View { let account : xmpp @Binding var selectedContacts : OrderedSet // already selected when going into the view @State var searchFieldInput = "" + + @State var isEditingSearchInput: Bool = false func matchesSearch(contact : MLContact) -> Bool { // TODO better lookup @@ -60,7 +62,8 @@ struct ContactPicker: View { } else { List { Section { - TextField(NSLocalizedString("Search contacts", comment: "placeholder in contact picker"), text: $searchFieldInput) + TextField(NSLocalizedString("Search contacts", comment: "placeholder in contact picker"), text: $searchFieldInput, onEditingChanged: { isEditingSearchInput = $0 }) + .addClearButton(isEditing: isEditingSearchInput, text:$searchFieldInput) } ForEach(Array(contacts.enumerated()), id: \.element) { idx, contact in if matchesSearch(contact: contact) { diff --git a/Monal/Classes/WelcomeLogIn.swift b/Monal/Classes/WelcomeLogIn.swift index e1d68f2585..671c2eb528 100644 --- a/Monal/Classes/WelcomeLogIn.swift +++ b/Monal/Classes/WelcomeLogIn.swift @@ -14,7 +14,9 @@ struct WelcomeLogIn: View { var delegate: SheetDismisserProtocol + @State private var isEditingJid: Bool = false @State private var jid: String = "" + @State private var isEditingPassword: Bool = false @State private var password: String = "" @State private var showAlert = false @@ -133,14 +135,17 @@ struct WelcomeLogIn: View { TextField(NSLocalizedString("user@domain.tld", comment: "placeholder when adding account"), text: Binding( get: { self.jid }, - set: { string in self.jid = string.lowercased().replacingOccurrences(of: " ", with: "") }) + set: { string in self.jid = string.lowercased().replacingOccurrences(of: " ", with: "") }), onEditingChanged: { isEditingJid = $0 } ) //ios15: .textInputAutocapitalization(.never) .autocapitalization(.none) .autocorrectionDisabled() .keyboardType(.emailAddress) + .addClearButton(isEditing: isEditingJid, text: $jid) SecureField(NSLocalizedString("Password", comment: "placeholder when adding account"), text: $password) + .addClearButton(isEditing: password.count > 0 + , text: $password) HStack() { Button(action: { From 6b01c6842748690b8815d853a6bc824afb8399ac Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 23 Feb 2024 15:52:03 +0100 Subject: [PATCH 064/115] --- 890 --- 6.2.0rc1 From dc4f8922748f1d0a79432afc5e6151921dc13a5d Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:57:15 +0100 Subject: [PATCH 065/115] Bump crates --- rust/Cargo.lock | 50 ++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 93a828e8e6..2ddfe7dec9 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -146,22 +146,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.50", ] [[package]] @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.49" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -272,9 +272,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -328,42 +328,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" From 2c3567d3f7427f3c4edff1148be8ef3063823e35 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:59:04 +0100 Subject: [PATCH 066/115] bump pods --- Monal/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Podfile.lock b/Monal/Podfile.lock index 37e6913219..b31906d026 100644 --- a/Monal/Podfile.lock +++ b/Monal/Podfile.lock @@ -53,7 +53,7 @@ PODS: - sqlite3/perf-threadsafe (3.45.1): - sqlite3/common - TOCropViewController (2.6.1) - - WebRTC-lib (121.0.0) + - WebRTC-lib (122.0.0) DEPENDENCIES: - ASN1Decoder @@ -121,7 +121,7 @@ SPEC CHECKSUMS: SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 sqlite3: 73b7fc691fdc43277614250e04d183740cb15078 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 - WebRTC-lib: cedcd0017ad839220b2dbce943e9c43c24dea036 + WebRTC-lib: 5e9d21edbcf9ce5cc66b7ec7f94f0c15a2779113 PODFILE CHECKSUM: f415f317e34fd11a687374f84ee8dfb14ebb29a6 From c35adac7b3e7f0970837a2e46fda98393f25c422 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 24 Feb 2024 11:54:29 +0100 Subject: [PATCH 067/115] ignore mediated MUC invites without a group jid --- Monal/Classes/MLMucProcessor.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index bcad1ea887..0558daf947 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -333,7 +333,13 @@ -(BOOL) processMessage:(XMPPMessage*) messageNode if(![messageNode.toUser isEqualToString:_account.connectionProperties.identity.jid]) return YES; //stop processing in MLMessageProcessor and ignore this invite - MLContact* inviteFrom = [MLContact createContactFromJid:[HelperTools splitJid:[messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"]][@"user"] andAccountNo:_account.accountNo]; + NSString* invitedMucJid = [HelperTools splitJid:[messageNode findFirst:@"{http://jabber.org/protocol/muc#user}x/invite@from"]][@"user"]; + if(invitedMucJid == nil) + { + DDLogError(@"mediated inivite does not include a MUC jid, ignoring invite"); + return YES; + } + MLContact* inviteFrom = [MLContact createContactFromJid:invitedMucJid andAccountNo:_account.accountNo]; DDLogInfo(@"Got mediated muc invite from %@ for %@...", inviteFrom, messageNode.fromUser); if(!inviteFrom.isSubscribedFrom) { From fb5964eae75765c7a9602229fd8b51ff62f0aa38 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 24 Feb 2024 18:31:09 +0100 Subject: [PATCH 068/115] Clean up image viewer --- Monal/Classes/ImageViewer.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Monal/Classes/ImageViewer.swift b/Monal/Classes/ImageViewer.swift index 4220243c51..f23d4135cf 100644 --- a/Monal/Classes/ImageViewer.swift +++ b/Monal/Classes/ImageViewer.swift @@ -42,10 +42,6 @@ struct ImageViewer: View { init(delegate: SheetDismisserProtocol, info:[String:AnyObject]) throws { self.delegate = delegate self.info = info - if #available(iOS 16, *) { - let mimeType = UTType(exportedAs:info["mimeType"] as! String, conformingTo:.data) - DDLogError("mimeType = \(String(describing:mimeType))") - } } // var body: some View { @@ -56,12 +52,12 @@ struct ImageViewer: View { // } var body: some View { - let image = UIImage(contentsOfFile:info["cacheFile"] as! String)! - ZStack(alignment: .top) { Color.background .edgesIgnoringSafeArea(.all) + let image = UIImage(contentsOfFile:info["cacheFile"] as! String)! + VStack { ZoomableContainer(maxScale:8.0, doubleTapScale:4.0) { if (info["mimeType"] as! String).hasPrefix("image/gif") { From 19fd35e78d0e1559c61249c6c6980dc8e5c73ad1 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 24 Feb 2024 19:54:23 +0100 Subject: [PATCH 069/115] Fix congestion handling in MLPipe --- Monal/Classes/MLPipe.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/MLPipe.m b/Monal/Classes/MLPipe.m index 3d158b74bd..394ec57d15 100755 --- a/Monal/Classes/MLPipe.m +++ b/Monal/Classes/MLPipe.m @@ -10,10 +10,11 @@ #import "HelperTools.h" #define kPipeBufferSize 4096 -static uint8_t _staticOutputBuffer[kPipeBufferSize+1]; //+1 for '\0' needed for logging the received raw bytes @interface MLPipe() { + uint8_t _staticOutputBuffer[kPipeBufferSize+1]; //+1 for '\0' needed for logging the received raw bytes + //buffer for writes to the output stream that can not be completed uint8_t* _outputBuffer; size_t _outputBufferByteCount; @@ -172,7 +173,8 @@ -(void) process //try to send remaining buffered data first if(_outputBufferByteCount > 0) { - DDLogDebug(@"trying to send buffered data: %lu bytes", (unsigned long)_outputBufferByteCount); + _outputBuffer[_outputBufferByteCount] = '\0'; //null termination for log output of raw string + DDLogDebug(@"trying to send buffered data(%lu): %s", (unsigned long)_outputBufferByteCount, _outputBuffer); NSInteger writtenLen = [_output write:_outputBuffer maxLength:_outputBufferByteCount]; if(writtenLen > 0) { @@ -233,7 +235,7 @@ -(void) process else DDLogDebug(@"pipe read %ld <= 0 bytes", (long)readLen); } while(readLen > 0 && [_input hasBytesAvailable] && [_output hasSpaceAvailable]); - //DDLogVerbose(@"pipe processing done"); + DDLogVerbose(@"pipe processing done"); } } From 407320284cc6f7e3ee9921aff1cd45acdba32666 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 24 Feb 2024 20:28:44 +0100 Subject: [PATCH 070/115] Clean up bundle fetch statistics on first login --- Monal/Classes/MLOMEMO.m | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index c0c8de1604..8219b4ffae 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -5,7 +5,6 @@ // Created by Friedrich Altheide on 21.06.20. // Copyright © 2020 Monal.im. All rights reserved. // -#import #import #import "MLOMEMO.h" @@ -499,25 +498,21 @@ -(void) queryOMEMOBundleFrom:(NSString*) jid andDevice:(NSNumber*) deviceid return; } - NSString* bundleNode = [NSString stringWithFormat:@"eu.siacs.conversations.axolotl.bundles:%@", deviceid]; + //update bundle fetch status + self.openBundleFetchCnt++; [[MLNotificationQueue currentQueue] postNotificationName:kMonalUpdateBundleFetchStatus object:self userInfo:@{ @"accountNo": self.account.accountNo, @"completed": @(self.closedBundleFetchCnt), @"all": @(self.openBundleFetchCnt + self.closedBundleFetchCnt) }]; + + NSString* bundleNode = [NSString stringWithFormat:@"eu.siacs.conversations.axolotl.bundles:%@", deviceid]; [self.account.pubsub fetchNode:bundleNode from:jid withItemsList:nil andHandler:$newHandlerWithInvalidation(self, handleBundleFetchResult, handleBundleFetchInvalidation, $ID(jid), $ID(rid, deviceid))]; if(self.state.openBundleFetches[jid] == nil) self.state.openBundleFetches[jid] = [NSMutableSet new]; [self.state.openBundleFetches[jid] addObject:deviceid]; - //update bundle fetch status - self.openBundleFetchCnt++; - [[MLNotificationQueue currentQueue] postNotificationName:kMonalUpdateBundleFetchStatus object:self userInfo:@{ - @"accountNo": self.account.accountNo, - @"completed": @(self.closedBundleFetchCnt), - @"all": @(self.openBundleFetchCnt + self.closedBundleFetchCnt) - }]; } //don't mark any devices as deleted in this invalidation handler (like we do for an error in the normal handler below), @@ -607,7 +602,16 @@ -(void) handleBundleWithInvalidEntryForJid:(NSString*) jid andRid:(NSNumber*) ri -(void) decrementBundleFetchCount { //use catchupDone for better UX on first login - if(self.openBundleFetchCnt > 1 || !self.state.catchupDone) + if(self.openBundleFetchCnt == 0 && self.state.catchupDone) + { + //update bundle fetch status (e.g. complete) + self.openBundleFetchCnt = 0; + self.closedBundleFetchCnt = 0; + [[MLNotificationQueue currentQueue] postNotificationName:kMonalFinishedOmemoBundleFetch object:self userInfo:@{ + @"accountNo": self.account.accountNo, + }]; + } + else { //update bundle fetch status (e.g. pending) self.openBundleFetchCnt--; @@ -618,15 +622,6 @@ -(void) decrementBundleFetchCount @"all": @(self.openBundleFetchCnt + self.closedBundleFetchCnt), }]; } - else - { - //update bundle fetch status (e.g. complete) - self.openBundleFetchCnt = 0; - self.closedBundleFetchCnt = 0; - [[MLNotificationQueue currentQueue] postNotificationName:kMonalFinishedOmemoBundleFetch object:self userInfo:@{ - @"accountNo": self.account.accountNo, - }]; - } } -(void) processOMEMOKeys:(MLXMLNode*) item forJid:(NSString*) jid andRid:(NSNumber*) rid From 77a7a3bf07d4e37065cd4feeba6c7e01438b6619 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 24 Feb 2024 22:24:10 +0100 Subject: [PATCH 071/115] Try to fix spurious alerts about new omemo devices, thanks steve --- Monal/Classes/MLOMEMO.m | 50 +++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 8219b4ffae..c9b2ee91f2 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -5,6 +5,7 @@ // Created by Friedrich Altheide on 21.06.20. // Copyright © 2020 Monal.im. All rights reserved. // +#import #import #import "MLOMEMO.h" @@ -366,9 +367,7 @@ -(void) queryOMEMODevices:(NSString*) jid if(publishedDevices) { - NSArray* deviceIds = [publishedDevices find:@"{eu.siacs.conversations.axolotl}list/device@id|uint"]; - NSSet* deviceSet = [[NSSet alloc] initWithArray:deviceIds]; - + NSSet* deviceSet = [[NSSet alloc] initWithArray:[publishedDevices find:@"{eu.siacs.conversations.axolotl}list/device@id|uint"]]; [self processOMEMODevices:deviceSet from:jid]; } @@ -382,7 +381,7 @@ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString*) { DDLogVerbose(@"Processing omemo devices from %@: %@", source, receivedDevices); - NSMutableSet* existingDevices = [NSMutableSet setWithArray:[self.monalSignalStore knownDevicesForAddressName:source]]; + NSMutableSet* existingDevices = [[self knownDevicesForAddressName:source] mutableCopy]; // ensure that we refetch bundles of devices with broken bundles again after some time NSSet* existingDevicesReqPendingFetch = [NSSet setWithArray:[self.monalSignalStore knownDevicesWithPendingBrokenSessionHandling:source]]; [existingDevices minusSet:existingDevicesReqPendingFetch]; @@ -436,23 +435,30 @@ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString*) -(void) handleOwnDevicelistUpdate:(NSSet*) receivedDevices { //check for new deviceids not previously known, but only if the devicelist is not empty - NSMutableSet* newDevices = [receivedDevices mutableCopy]; - [newDevices minusSet:self.ownDeviceList]; - for(NSNumber* device in newDevices) - if([device unsignedIntValue] != self.monalSignalStore.deviceid) - { - DDLogWarn(@"Got new deviceid %@ for own account %@", device, self.account.connectionProperties.identity.jid); - UNMutableNotificationContent* content = [UNMutableNotificationContent new]; - content.title = NSLocalizedString(@"New omemo device", @"");; - content.subtitle = self.account.connectionProperties.identity.jid; - content.body = [NSString stringWithFormat:NSLocalizedString(@"Detected a new omemo device on your account: %@", @""), device]; - content.sound = [UNNotificationSound defaultSound]; - content.categoryIdentifier = @"simple"; - UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:[NSString stringWithFormat:@"newOwnOmemoDevice::%@::%@", self.account.connectionProperties.identity.jid, device] content:content trigger:nil]; - NSError* error = [HelperTools postUserNotificationRequest:request]; - if(error) - DDLogError(@"Error posting new deviceid notification: %@", error); - } + if([self.ownDeviceList count] > 0) + { + NSMutableSet* newDevices = [receivedDevices mutableCopy]; + [newDevices minusSet:self.ownDeviceList]; + //don't alert for devices with broken bundles + NSSet* existingDevicesReqPendingFetch = [NSSet setWithArray:[self.monalSignalStore knownDevicesWithPendingBrokenSessionHandling:self.account.connectionProperties.identity.jid]]; + [newDevices minusSet:existingDevicesReqPendingFetch]; + //alert for all devices now still listed in newDevices + for(NSNumber* device in newDevices) + if([device unsignedIntValue] != self.monalSignalStore.deviceid) + { + DDLogWarn(@"Got new deviceid %@ for own account %@", device, self.account.connectionProperties.identity.jid); + UNMutableNotificationContent* content = [UNMutableNotificationContent new]; + content.title = NSLocalizedString(@"New omemo device", @"");; + content.subtitle = self.account.connectionProperties.identity.jid; + content.body = [NSString stringWithFormat:NSLocalizedString(@"Detected a new omemo device on your account: %@", @""), device]; + content.sound = [UNNotificationSound defaultSound]; + content.categoryIdentifier = @"simple"; + UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:[NSString stringWithFormat:@"newOwnOmemoDevice::%@::%@", self.account.connectionProperties.identity.jid, device] content:content trigger:nil]; + NSError* error = [HelperTools postUserNotificationRequest:request]; + if(error) + DDLogError(@"Error posting new deviceid notification: %@", error); + } + } //update own devicelist (this can be an empty list, if the list on our server is empty) self.ownDeviceList = [receivedDevices mutableCopy]; @@ -954,7 +960,7 @@ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullab } //check if we found omemo keys of at least one of the recipients or more than 1 own device, otherwise don't encrypt anything - NSSet* myDevices = [NSSet setWithArray:[self.monalSignalStore knownDevicesForAddressName:self.account.connectionProperties.identity.jid]]; + NSSet* myDevices = [self knownDevicesForAddressName:self.account.connectionProperties.identity.jid]; if(contactDeviceMap.count > 0 || myDevices.count > 1) { //add encryption for all of our own devices to contactDeviceMap From 8f75ad899c1c474ee6df23e80d1422cb5808241d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 26 Feb 2024 01:50:08 +0100 Subject: [PATCH 072/115] Don't try to decrypt own muc-reflected omemo messages Fixes #956 --- Monal/Classes/MLMessageProcessor.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index 6346ba0b12..2475532b54 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -422,6 +422,13 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } } + //ignore encrypted messages coming from our own device id (most probably a muc reflection) + BOOL sentByOwnOmemoDevice = NO; +#ifndef DISABLE_OMEMO + if([messageNode check:@"{eu.siacs.conversations.axolotl}encrypted/header@sid|uint"]) + sentByOwnOmemoDevice = ((NSNumber*)[messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/header@sid|uint"]).unsignedIntValue == [account.omemo getDeviceId].unsignedIntValue; +#endif + //handle message retraction (XEP-0424) if([messageNode check:@"{urn:xmpp:fasten:0}apply-to/{urn:xmpp:message-retract:0}retract"]) { @@ -479,7 +486,8 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag @"contact": [MLContact createContactFromJid:buddyName andAccountNo:account.accountNo], }]; } - else if([messageNode check:@"body#"] || decrypted) + //ignore encrypted body messages coming from our own device id (most probably a muc reflection) + else if(([messageNode check:@"body#"] || decrypted) && !sentByOwnOmemoDevice) { BOOL unread = YES; BOOL showAlert = YES; From 5f89c8ace134130ca4196ec9d5b9586130530a01 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 27 Feb 2024 05:13:36 +0100 Subject: [PATCH 073/115] Fix contact refresh on removal, fixes #955 --- Monal/Classes/ActiveChatsViewController.m | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index c3292d6152..db05d4e368 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -270,24 +270,23 @@ -(void) handleContactRemoved:(NSNotification*) notification { MLContact* removedContact = [notification.userInfo objectForKey:@"contact"]; if(removedContact == nil) - { unreachable(); - } - //update red dot dispatch_async(dispatch_get_main_queue(), ^{ + DDLogInfo(@"Contact removed, refreshing active chats..."); + + //update red dot [self configureComposeButton]; - }); - - // ignore all removals that aren't in foreground - if([removedContact isEqualToContact:[MLNotificationManager sharedInstance].currentContact] == NO) - return; - dispatch_async(dispatch_get_main_queue(), ^{ - DDLogInfo(@"Contact removed, closing chat view..."); + // remove contact from activechats table [self refreshDisplay]; - // open placeholder - [self presentChatWithContact:nil]; + + // open placeholder if the removed contact was "in foreground" + if([removedContact isEqualToContact:[MLNotificationManager sharedInstance].currentContact]) + { + DDLogInfo(@"Contact removed, closing chat view..."); + [self presentChatWithContact:nil]; + } }); } From 165c45972ac2e39851b507c616888d5ab8a8f01d Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:00:15 +0100 Subject: [PATCH 074/115] groups: subject / name change UI WIP: Only show the new UI when the user is an moderator --- Monal/Classes/ContactDetails.swift | 16 ++++++ Monal/Classes/EditGroupDescription.swift | 67 ++++++++++++++++++++++ Monal/Classes/EditGroupName.swift | 58 +++++++++++++++++++ Monal/Classes/GroupDetailsEdit.swift | 73 ++++++++++++++++++++++++ Monal/Monal.xcodeproj/project.pbxproj | 12 ++++ 5 files changed, 226 insertions(+) create mode 100644 Monal/Classes/EditGroupDescription.swift create mode 100644 Monal/Classes/EditGroupName.swift create mode 100644 Monal/Classes/GroupDetailsEdit.swift diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index 03db2e965a..c721bb9709 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -348,6 +348,22 @@ struct ContactDetails: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .navigationBarTitle(contact.contactDisplayName as String, displayMode: .inline) + .navigationBarGroupEditButton(contact: contact) + } +} + +extension View { + func navigationBarGroupEditButton(contact: ObservableKVOWrapper) -> some View { + if contact.isGroup { + return AnyView(self.toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + NavigationLink(destination: LazyClosureView(GroupDetailsEdit(contact: contact))) { + Text("Edit") + } + } + }) + } + return AnyView(self) } } diff --git a/Monal/Classes/EditGroupDescription.swift b/Monal/Classes/EditGroupDescription.swift new file mode 100644 index 0000000000..344e59f6a1 --- /dev/null +++ b/Monal/Classes/EditGroupDescription.swift @@ -0,0 +1,67 @@ +// +// EditGroupDescription.swift +// Monal +// +// Created by Friedrich Altheide on 27.02.24. +// Copyright © 2024 monal-im.org. All rights reserved. +// + +import SwiftUI + +extension View { + func versionConditionalLineLimit(_ limit: ClosedRange) -> some View { + if #available(iOS 16.0, *) { + return self.lineLimit(10...50) + } else { + return self + } + } +} + +@available(iOS 15.0, *) +struct EditGroupDescription: View { + @ObservedObject var contact: ObservableKVOWrapper + private let account: xmpp? + + @State private var subject: String + @State private var isEditingSubject: Bool = false + + @Environment(\.dismiss) var dismiss + + init(contact: ObservableKVOWrapper) { + MLAssert(contact.isGroup) + + _subject = State(wrappedValue: contact.obj.groupSubject) + self.contact = contact + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp + } + + var body: some View { + NavigationView { + VStack { + Form { + Section { + TextField(NSLocalizedString("Group Description (optional)", comment: "placeholder when editing a group description"), text: $subject, onEditingChanged: { isEditingSubject = $0 }) + .multilineTextAlignment(.leading) + .versionConditionalLineLimit(10...50) + .addClearButton(isEditing: isEditingSubject, text:$subject) + } + } + } + .navigationTitle("Group description") + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Abort") { + dismiss() + } + } + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + self.account!.mucProcessor.changeSubject(ofMuc: contact.contactJid, to: self.subject) + dismiss() + } + } + } + } + } +} diff --git a/Monal/Classes/EditGroupName.swift b/Monal/Classes/EditGroupName.swift new file mode 100644 index 0000000000..c58a29f2cc --- /dev/null +++ b/Monal/Classes/EditGroupName.swift @@ -0,0 +1,58 @@ +// +// EditGroupName.swift +// Monal +// +// Created by Friedrich Altheide on 24.02.24. +// Copyright © 2024 monal-im.org. All rights reserved. +// + +import SwiftUI + +@available(iOS 15.0, *) +struct EditGroupName: View { + @ObservedObject var contact: ObservableKVOWrapper + private let account: xmpp? + + @State private var groupName: String + @State private var isEditingGroupName: Bool = false + + @Environment(\.dismiss) var dismiss + + init(contact: ObservableKVOWrapper) { + MLAssert(contact.isGroup) + + _groupName = State(wrappedValue: contact.obj.contactDisplayName) + self.contact = contact + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp + + } + + var body: some View { + NavigationView { + VStack { + Form { + Section { + TextField(NSLocalizedString("Group Name (optional)", comment: "placeholder when editing a group name"), text: $groupName, onEditingChanged: { isEditingGroupName = $0 }) + .autocorrectionDisabled() + .autocapitalization(.none) + .addClearButton(isEditing: isEditingGroupName, text:$groupName) + } + } + } + .navigationTitle("Groupname") + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Abort") { + dismiss() + } + } + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + self.account!.mucProcessor.changeName(ofMuc: contact.contactJid, to: self.groupName) + dismiss() + } + } + } + } + } +} diff --git a/Monal/Classes/GroupDetailsEdit.swift b/Monal/Classes/GroupDetailsEdit.swift new file mode 100644 index 0000000000..8675ac20cb --- /dev/null +++ b/Monal/Classes/GroupDetailsEdit.swift @@ -0,0 +1,73 @@ +// +// GroupDetailsEdit.swift +// Monal +// +// Created by Friedrich Altheide on 23.02.24. +// Copyright © 2024 monal-im.org. All rights reserved. +// + +import SwiftUI + +struct GroupDetailsEdit: View { + @ObservedObject var contact: ObservableKVOWrapper + private let account: xmpp? + + @State private var showingSheet = false + + init(contact: ObservableKVOWrapper) { + MLAssert(contact.isGroup) + + self.contact = contact + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp + } + + var body: some View { + Form { + Section { + HStack { + Spacer() + Image(uiImage: contact.avatar) + .resizable() + .scaledToFit() + .accessibilityLabel((contact.obj.mucType == "group") ? "Group Avatar" : "Channel Avatar") + .frame(width: 150, height: 150, alignment: .center) + .shadow(radius: 7) + Spacer() + } + } + Section { + if #available(iOS 15.0, *) { + Button(action: { + showingSheet.toggle() + }) { + HStack { + Image(systemName: "person.2") + Text(contact.contactDisplayName as String) + Spacer() + } + } + .sheet(isPresented: $showingSheet) { + LazyClosureView(EditGroupName(contact: contact)) + } + Button(action: { + showingSheet.toggle() + }) { + HStack { + Image(systemName: "pencil") + Text((contact.obj.mucType == "group") ? "Group description" : "Channel description") + Spacer() + } + } + .sheet(isPresented: $showingSheet) { + LazyClosureView(EditGroupDescription(contact: contact)) + } + } + } + } + .navigationTitle("Edit group") + } +} + +#Preview { + GroupDetailsEdit(contact:ObservableKVOWrapper(MLContact.makeDummyContact(0))) +} diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index 2ccb090e27..ec078a7ca9 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -177,6 +177,8 @@ C13A0BCE26E78B7B00987E29 /* ContactDetailsHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19C919A26E26AF000F8CC57 /* ContactDetailsHeader.swift */; }; C13EBB8E24DC685C008AADDA /* MLPrivacySettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C13EBB8D24DC685C008AADDA /* MLPrivacySettingsViewController.m */; }; C1414E9D24312F0100948788 /* MLChatMapsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C1414E9C24312F0100948788 /* MLChatMapsCell.m */; }; + C153825F2B89BBE600EA83EC /* GroupDetailsEdit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C153825E2B89BBE600EA83EC /* GroupDetailsEdit.swift */; }; + C15382622B89C38300EA83EC /* EditGroupName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15382612B89C38300EA83EC /* EditGroupName.swift */; }; C15489B925680BBE00BBA2F0 /* MLQRCodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15489B825680BBE00BBA2F0 /* MLQRCodeScanner.swift */; }; C158D40025A0AB810005AA40 /* MLMucProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = C158D3FE25A0AB810005AA40 /* MLMucProcessor.h */; }; C158D41425A0AC630005AA40 /* MLMucProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C158D41225A0AC630005AA40 /* MLMucProcessor.m */; }; @@ -200,6 +202,7 @@ C1D7D7B0283FB4E700401389 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 26470F511835C4080069E3E0 /* Media.xcassets */; }; C1E1EC7B286A025F0097EC74 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = C1E1EC7A286A025F0097EC74 /* SwiftSoup */; }; C1E4654824EE517000CA5AAF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4654624EE517000CA5AAF /* Localizable.strings */; }; + C1E8A7F72B8E47C300760220 /* EditGroupDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E8A7F62B8E47C300760220 /* EditGroupDescription.swift */; }; C1F5C7A92775DA000001F295 /* MLContactSoftwareVersionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = C1F5C7A72775DA000001F295 /* MLContactSoftwareVersionInfo.h */; }; C1F5C7AA2775DA000001F295 /* MLContactSoftwareVersionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F5C7A82775DA000001F295 /* MLContactSoftwareVersionInfo.m */; }; C1F5C7AC2777621B0001F295 /* ContactResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F5C7AB2777621B0001F295 /* ContactResources.swift */; }; @@ -634,6 +637,8 @@ C13EBB8D24DC685C008AADDA /* MLPrivacySettingsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLPrivacySettingsViewController.m; sourceTree = ""; }; C1414E9B24312F0100948788 /* MLChatMapsCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLChatMapsCell.h; sourceTree = ""; }; C1414E9C24312F0100948788 /* MLChatMapsCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLChatMapsCell.m; sourceTree = ""; }; + C153825E2B89BBE600EA83EC /* GroupDetailsEdit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupDetailsEdit.swift; sourceTree = ""; }; + C15382612B89C38300EA83EC /* EditGroupName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupName.swift; sourceTree = ""; }; C15489B825680BBE00BBA2F0 /* MLQRCodeScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MLQRCodeScanner.swift; sourceTree = ""; }; C1567E3528255C64006E9637 /* Monal.macos.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Monal.macos.entitlements; sourceTree = ""; }; C1567E3628255C64006E9637 /* Monal.ios.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Monal.ios.entitlements; sourceTree = ""; }; @@ -736,6 +741,7 @@ C1E856A728DECF5F00B104E9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = external/eu.lproj/Settings.strings; sourceTree = ""; }; C1E856A828DECF5F00B104E9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = external/eu.lproj/iosShare.strings; sourceTree = ""; }; C1E856A928DECF5F00B104E9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = external/eu.lproj/Localizable.strings; sourceTree = ""; }; + C1E8A7F62B8E47C300760220 /* EditGroupDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupDescription.swift; sourceTree = ""; }; C1F0AD15288BCE6F00BB0182 /* Alpha.Monal.ios.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Alpha.Monal.ios.entitlements; sourceTree = ""; }; C1F5C7A72775DA000001F295 /* MLContactSoftwareVersionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLContactSoftwareVersionInfo.h; sourceTree = ""; }; C1F5C7A82775DA000001F295 /* MLContactSoftwareVersionInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLContactSoftwareVersionInfo.m; sourceTree = ""; }; @@ -1101,6 +1107,9 @@ C19C919A26E26AF000F8CC57 /* ContactDetailsHeader.swift */, 3D5A91412842B4AE008CE57E /* MemberList.swift */, C18967C62B81F61B0073C7C5 /* ChannelMemberList.swift */, + C153825E2B89BBE600EA83EC /* GroupDetailsEdit.swift */, + C15382612B89C38300EA83EC /* EditGroupName.swift */, + C1E8A7F62B8E47C300760220 /* EditGroupDescription.swift */, ); name = "Contact Details"; sourceTree = ""; @@ -2040,6 +2049,8 @@ C1943A4C25309A9D0036172F /* MLReloadCell.m in Sources */, 262E51971AD8CB7200788351 /* MLTextInputCell.m in Sources */, 84E55E7D2964424E003E191A /* ActiveChatsViewController.m in Sources */, + C15382622B89C38300EA83EC /* EditGroupName.swift in Sources */, + C1E8A7F72B8E47C300760220 /* EditGroupDescription.swift in Sources */, 263DFAC32187D0E00038E716 /* MLLinkCell.m in Sources */, 3D65B78D27234B74005A30F4 /* ContactDetails.swift in Sources */, E89DD32525C6626400925F62 /* MLFileTransferDataCell.m in Sources */, @@ -2070,6 +2081,7 @@ 26A78ED823C2B59400C7CF40 /* MLPlaceholderViewController.m in Sources */, C12436142434AB5D00B8F074 /* MLAttributedLabel.m in Sources */, C13A0BCE26E78B7B00987E29 /* ContactDetailsHeader.swift in Sources */, + C153825F2B89BBE600EA83EC /* GroupDetailsEdit.swift in Sources */, 3D85E587282AE523006F5B3A /* OmemoQrCodeView.swift in Sources */, 849A53E4287135B2007E941A /* MLVoIPProcessor.m in Sources */, C117F7E12B086390001F2BC6 /* CreateGroupMenu.swift in Sources */, From b957efeaa1ff30f0edca3883e5af8049ddc6a108 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:16:35 +0100 Subject: [PATCH 075/115] rename file --- ...{EditGroupDescription.swift => EditGroupSubject.swift} | 4 ++-- Monal/Classes/GroupDetailsEdit.swift | 2 +- Monal/Monal.xcodeproj/project.pbxproj | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename Monal/Classes/{EditGroupDescription.swift => EditGroupSubject.swift} (96%) diff --git a/Monal/Classes/EditGroupDescription.swift b/Monal/Classes/EditGroupSubject.swift similarity index 96% rename from Monal/Classes/EditGroupDescription.swift rename to Monal/Classes/EditGroupSubject.swift index 344e59f6a1..821b2fb367 100644 --- a/Monal/Classes/EditGroupDescription.swift +++ b/Monal/Classes/EditGroupSubject.swift @@ -1,5 +1,5 @@ // -// EditGroupDescription.swift +// EditGroupSubject.swift // Monal // // Created by Friedrich Altheide on 27.02.24. @@ -19,7 +19,7 @@ extension View { } @available(iOS 15.0, *) -struct EditGroupDescription: View { +struct EditGroupSubject: View { @ObservedObject var contact: ObservableKVOWrapper private let account: xmpp? diff --git a/Monal/Classes/GroupDetailsEdit.swift b/Monal/Classes/GroupDetailsEdit.swift index 8675ac20cb..9f2268cbf7 100644 --- a/Monal/Classes/GroupDetailsEdit.swift +++ b/Monal/Classes/GroupDetailsEdit.swift @@ -59,7 +59,7 @@ struct GroupDetailsEdit: View { } } .sheet(isPresented: $showingSheet) { - LazyClosureView(EditGroupDescription(contact: contact)) + LazyClosureView(EditGroupSubject(contact: contact)) } } } diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index ec078a7ca9..90632a3633 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -202,7 +202,7 @@ C1D7D7B0283FB4E700401389 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 26470F511835C4080069E3E0 /* Media.xcassets */; }; C1E1EC7B286A025F0097EC74 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = C1E1EC7A286A025F0097EC74 /* SwiftSoup */; }; C1E4654824EE517000CA5AAF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4654624EE517000CA5AAF /* Localizable.strings */; }; - C1E8A7F72B8E47C300760220 /* EditGroupDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E8A7F62B8E47C300760220 /* EditGroupDescription.swift */; }; + C1E8A7F72B8E47C300760220 /* EditGroupSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E8A7F62B8E47C300760220 /* EditGroupSubject.swift */; }; C1F5C7A92775DA000001F295 /* MLContactSoftwareVersionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = C1F5C7A72775DA000001F295 /* MLContactSoftwareVersionInfo.h */; }; C1F5C7AA2775DA000001F295 /* MLContactSoftwareVersionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F5C7A82775DA000001F295 /* MLContactSoftwareVersionInfo.m */; }; C1F5C7AC2777621B0001F295 /* ContactResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F5C7AB2777621B0001F295 /* ContactResources.swift */; }; @@ -741,7 +741,7 @@ C1E856A728DECF5F00B104E9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = external/eu.lproj/Settings.strings; sourceTree = ""; }; C1E856A828DECF5F00B104E9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = external/eu.lproj/iosShare.strings; sourceTree = ""; }; C1E856A928DECF5F00B104E9 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = external/eu.lproj/Localizable.strings; sourceTree = ""; }; - C1E8A7F62B8E47C300760220 /* EditGroupDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupDescription.swift; sourceTree = ""; }; + C1E8A7F62B8E47C300760220 /* EditGroupSubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupSubject.swift; sourceTree = ""; }; C1F0AD15288BCE6F00BB0182 /* Alpha.Monal.ios.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Alpha.Monal.ios.entitlements; sourceTree = ""; }; C1F5C7A72775DA000001F295 /* MLContactSoftwareVersionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLContactSoftwareVersionInfo.h; sourceTree = ""; }; C1F5C7A82775DA000001F295 /* MLContactSoftwareVersionInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLContactSoftwareVersionInfo.m; sourceTree = ""; }; @@ -1109,7 +1109,7 @@ C18967C62B81F61B0073C7C5 /* ChannelMemberList.swift */, C153825E2B89BBE600EA83EC /* GroupDetailsEdit.swift */, C15382612B89C38300EA83EC /* EditGroupName.swift */, - C1E8A7F62B8E47C300760220 /* EditGroupDescription.swift */, + C1E8A7F62B8E47C300760220 /* EditGroupSubject.swift */, ); name = "Contact Details"; sourceTree = ""; @@ -2050,7 +2050,7 @@ 262E51971AD8CB7200788351 /* MLTextInputCell.m in Sources */, 84E55E7D2964424E003E191A /* ActiveChatsViewController.m in Sources */, C15382622B89C38300EA83EC /* EditGroupName.swift in Sources */, - C1E8A7F72B8E47C300760220 /* EditGroupDescription.swift in Sources */, + C1E8A7F72B8E47C300760220 /* EditGroupSubject.swift in Sources */, 263DFAC32187D0E00038E716 /* MLLinkCell.m in Sources */, 3D65B78D27234B74005A30F4 /* ContactDetails.swift in Sources */, E89DD32525C6626400925F62 /* MLFileTransferDataCell.m in Sources */, From 15ecbecbdf3d2eda2344c871813cc77f4d682ae9 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:11:28 +0100 Subject: [PATCH 076/115] More ObservableKVOWrapper & group avatar changes --- Monal/Classes/ContactEntry.swift | 10 ++++---- Monal/Classes/ContactPicker.swift | 35 ++++++++++++++++------------ Monal/Classes/CreateGroupMenu.swift | 4 ++-- Monal/Classes/GroupDetailsEdit.swift | 13 +++++++++++ Monal/Classes/MLContact.h | 2 ++ Monal/Classes/MemberList.swift | 2 +- Monal/Classes/SwiftHelpers.swift | 12 ++++++++-- 7 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Monal/Classes/ContactEntry.swift b/Monal/Classes/ContactEntry.swift index e159fb182a..9ff3eac03f 100644 --- a/Monal/Classes/ContactEntry.swift +++ b/Monal/Classes/ContactEntry.swift @@ -9,7 +9,7 @@ import SwiftUI struct ContactEntry: View { - let contact : MLContact + let contact : ObservableKVOWrapper var body:some View { ZStack(alignment: .topLeading) { @@ -27,17 +27,17 @@ struct ContactEntry: View { } #Preview { - ContactEntry(contact:MLContact.makeDummyContact(0)) + ContactEntry(contact:ObservableKVOWrapper(MLContact.makeDummyContact(0))) } #Preview { - ContactEntry(contact:MLContact.makeDummyContact(1)) + ContactEntry(contact:ObservableKVOWrapper(MLContact.makeDummyContact(1))) } #Preview { - ContactEntry(contact:MLContact.makeDummyContact(2)) + ContactEntry(contact:ObservableKVOWrapper(MLContact.makeDummyContact(2))) } #Preview { - ContactEntry(contact:MLContact.makeDummyContact(3)) + ContactEntry(contact:ObservableKVOWrapper(MLContact.makeDummyContact(3))) } diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index 2f61e6e06e..b2fec202f4 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -11,7 +11,7 @@ import monalxmpp import OrderedCollections struct ContactPickerEntry: View { - let contact : MLContact + let contact : ObservableKVOWrapper let isPicked: Bool var body:some View { @@ -38,20 +38,31 @@ struct ContactPickerEntry: View { struct ContactPicker: View { @Environment(\.presentationMode) private var presentationMode - let contacts : [MLContact] - let account : xmpp - @Binding var selectedContacts : OrderedSet // already selected when going into the view + @State var contacts: [ObservableKVOWrapper] + let account: xmpp + @Binding var selectedContacts : OrderedSet> // already selected when going into the view @State var searchFieldInput = "" - + @State var isEditingSearchInput: Bool = false - func matchesSearch(contact : MLContact) -> Bool { + init(account: xmpp, selectedContacts: Binding>>) { + self.account = account + self._selectedContacts = selectedContacts + + var contactsTmp: [ObservableKVOWrapper] = Array() + for contact in DataLayer.sharedInstance().possibleGroupMembers(forAccount: account.accountNo) { + contactsTmp.append(ObservableKVOWrapper(contact)) + } + _contacts = State(wrappedValue: contactsTmp) + } + + func matchesSearch(contact: ObservableKVOWrapper) -> Bool { // TODO better lookup if searchFieldInput.isEmpty == true { return true } else { - return contact.contactDisplayName.lowercased().contains(searchFieldInput.lowercased()) || - contact.contactJid.contains(searchFieldInput.lowercased()) + return (contact.contactDisplayName as String).lowercased().contains(searchFieldInput.lowercased()) || + (contact.contactJid as String).contains(searchFieldInput.lowercased()) } } @@ -65,7 +76,7 @@ struct ContactPicker: View { TextField(NSLocalizedString("Search contacts", comment: "placeholder in contact picker"), text: $searchFieldInput, onEditingChanged: { isEditingSearchInput = $0 }) .addClearButton(isEditing: isEditingSearchInput, text:$searchFieldInput) } - ForEach(Array(contacts.enumerated()), id: \.element) { idx, contact in + ForEach(Array(contacts.enumerated()), id: \.element.obj.contactJid) { idx, contact in if matchesSearch(contact: contact) { let contactIsSelected = self.selectedContacts.contains(contact); ContactPickerEntry(contact: contact, isPicked: contactIsSelected) @@ -91,10 +102,4 @@ struct ContactPicker: View { } } } - - init(account: xmpp, selectedContacts: Binding>) { - self.account = account - self._selectedContacts = selectedContacts - self.contacts = DataLayer.sharedInstance().possibleGroupMembers(forAccount: account.accountNo) - } } diff --git a/Monal/Classes/CreateGroupMenu.swift b/Monal/Classes/CreateGroupMenu.swift index 0bbb79a4a2..e92d983f1b 100644 --- a/Monal/Classes/CreateGroupMenu.swift +++ b/Monal/Classes/CreateGroupMenu.swift @@ -22,7 +22,7 @@ struct CreateGroupMenu: View { @State private var showAlert = false // note: dismissLabel is not accessed but defined at the .alert() section @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) - @State private var selectedContacts : OrderedSet = [] + @State private var selectedContacts : OrderedSet> = [] @State private var isEditingGroupName = false @@ -106,7 +106,7 @@ struct CreateGroupMenu: View { } if(self.selectedContacts.count > 0) { Section(header: Text("Selected Group Members")) { - ForEach(self.selectedContacts, id: \.contactJid) { contact in + ForEach(self.selectedContacts, id: \.obj.contactJid) { contact in ContactEntry(contact: contact) } .onDelete(perform: { indexSet in diff --git a/Monal/Classes/GroupDetailsEdit.swift b/Monal/Classes/GroupDetailsEdit.swift index 9f2268cbf7..44a4e4e539 100644 --- a/Monal/Classes/GroupDetailsEdit.swift +++ b/Monal/Classes/GroupDetailsEdit.swift @@ -7,18 +7,22 @@ // import SwiftUI +import _PhotosUI_SwiftUI struct GroupDetailsEdit: View { @ObservedObject var contact: ObservableKVOWrapper private let account: xmpp? @State private var showingSheet = false + @State private var inputImage: UIImage? + @State private var showingImagePicker = false init(contact: ObservableKVOWrapper) { MLAssert(contact.isGroup) self.contact = contact self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp + _inputImage = State(initialValue: contact.avatar) } var body: some View { @@ -32,8 +36,14 @@ struct GroupDetailsEdit: View { .accessibilityLabel((contact.obj.mucType == "group") ? "Group Avatar" : "Channel Avatar") .frame(width: 150, height: 150, alignment: .center) .shadow(radius: 7) + .onTapGesture { + showingImagePicker = true + } Spacer() } + .sheet(isPresented:$showingImagePicker) { + ImagePicker(image:$inputImage) + } } Section { if #available(iOS 15.0, *) { @@ -65,6 +75,9 @@ struct GroupDetailsEdit: View { } } .navigationTitle("Edit group") + .onChange(of:inputImage) { _ in + self.account!.mucProcessor.publishAvatar(inputImage, forMuc: contact.contactJid) + } } } diff --git a/Monal/Classes/MLContact.h b/Monal/Classes/MLContact.h index ee4bce8cc3..49aa660b8c 100644 --- a/Monal/Classes/MLContact.h +++ b/Monal/Classes/MLContact.h @@ -111,6 +111,8 @@ FOUNDATION_EXPORT NSString* const kAskSubscribe; -(void) clearHistory; -(void) removeShareInteractions; +-(NSUInteger) hash; + @end NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 1f86e63fab..390d966dc9 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -18,7 +18,7 @@ struct MemberList: View { private var ownAffiliation: String = "none"; @State private var openAccountSelection : Bool = false - @State private var contactsToAdd : OrderedSet = [] + @State private var contactsToAdd : OrderedSet> = [] @State private var showAlert = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) diff --git a/Monal/Classes/SwiftHelpers.swift b/Monal/Classes/SwiftHelpers.swift index acc6ab408e..3db5769e0c 100644 --- a/Monal/Classes/SwiftHelpers.swift +++ b/Monal/Classes/SwiftHelpers.swift @@ -69,7 +69,7 @@ class KVOObserver: NSObject { } @dynamicMemberLookup -public class ObservableKVOWrapper: ObservableObject { +public class ObservableKVOWrapper: ObservableObject, Hashable { public var obj: ObjType private var observedMembers: NSMutableSet = NSMutableSet() private var observers: [KVOObserver] = Array() @@ -113,7 +113,7 @@ public class ObservableKVOWrapper: ObservableObject { self.setWrapper(for:member, value:newValue as AnyObject?) } } - + public subscript(dynamicMember member: String) -> T { get { return self.getWrapper(for:member) as! T @@ -122,6 +122,14 @@ public class ObservableKVOWrapper: ObservableObject { self.setWrapper(for:member, value:newValue as AnyObject?) } } + + public static func == (lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { + return lhs.obj.isEqual(rhs.obj) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(self.obj.hashValue) + } } @objcMembers From 3ca1faab9fd7c89960f7bae141b566d85c26a83c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 27 Feb 2024 15:50:49 +0100 Subject: [PATCH 077/115] Don't filter broken devices for devicelist alerts --- Monal/Classes/MLOMEMO.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index c9b2ee91f2..50f4999ed5 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -439,9 +439,6 @@ -(void) handleOwnDevicelistUpdate:(NSSet*) receivedDevices { NSMutableSet* newDevices = [receivedDevices mutableCopy]; [newDevices minusSet:self.ownDeviceList]; - //don't alert for devices with broken bundles - NSSet* existingDevicesReqPendingFetch = [NSSet setWithArray:[self.monalSignalStore knownDevicesWithPendingBrokenSessionHandling:self.account.connectionProperties.identity.jid]]; - [newDevices minusSet:existingDevicesReqPendingFetch]; //alert for all devices now still listed in newDevices for(NSNumber* device in newDevices) if([device unsignedIntValue] != self.monalSignalStore.deviceid) From ea3c38e6871844f8a3191f2adcfcc90a2d9afa9b Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 27 Feb 2024 20:16:31 +0100 Subject: [PATCH 078/115] Implement === operator for KVO wrapped objects --- Monal/Classes/SwiftHelpers.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Monal/Classes/SwiftHelpers.swift b/Monal/Classes/SwiftHelpers.swift index 3db5769e0c..13e8b9e6b1 100644 --- a/Monal/Classes/SwiftHelpers.swift +++ b/Monal/Classes/SwiftHelpers.swift @@ -126,6 +126,11 @@ public class ObservableKVOWrapper: ObservableObject, Hashable public static func == (lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { return lhs.obj.isEqual(rhs.obj) } + + // see https://stackoverflow.com/a/33320737 + public static func === (lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { + return lhs.obj === rhs.obj + } public func hash(into hasher: inout Hasher) { hasher.combine(self.obj.hashValue) From 289e7de9a3f02e17fbec7e557ed54194667b72f2 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 27 Feb 2024 20:16:55 +0100 Subject: [PATCH 079/115] Don't strip debug symbols for all builds --- Monal/Monal.xcodeproj/project.pbxproj | 170 ++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index 90632a3633..e5d7f9b90f 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -2445,6 +2445,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = Monal.macos.entitlements; COMPILER_INDEX_STORE_ENABLE = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; @@ -2473,12 +2474,16 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal"; PRODUCT_MODULE_NAME = Monal; PRODUCT_NAME = Monal; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2501,6 +2506,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/NotificationService.macos.entitlements; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2512,6 +2518,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.notificationService"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2530,6 +2538,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/NotificationService.macos.entitlements; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2541,6 +2550,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.notificationService"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2559,6 +2570,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/NotificationService.macos.entitlements; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2570,6 +2582,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.notificationService"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2616,9 +2630,12 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2643,7 +2660,11 @@ LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.0; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = NO; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2676,9 +2697,11 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = Monal.macos.entitlements; COMPILER_INDEX_STORE_ENABLE = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; + ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -2703,12 +2726,16 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal"; PRODUCT_MODULE_NAME = Monal; PRODUCT_NAME = Monal; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2727,6 +2754,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = shareSheet.entitlements; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; @@ -2738,6 +2766,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.shareSheet; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.shareSheet"; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2752,6 +2782,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = shareSheet.entitlements; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; @@ -2763,6 +2794,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.shareSheet; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.shareSheet"; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2777,6 +2810,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = shareSheet.entitlements; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; @@ -2788,6 +2822,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.shareSheet; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.shareSheet"; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2803,6 +2839,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2822,6 +2859,9 @@ PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2841,6 +2881,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2859,6 +2900,9 @@ PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2878,6 +2922,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2896,6 +2941,9 @@ PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -2946,9 +2994,12 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -2976,7 +3027,11 @@ LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.0; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = NO; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTS_MACCATALYST = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3009,9 +3064,11 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = Monal.macos.entitlements; COMPILER_INDEX_STORE_ENABLE = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; + ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -3037,12 +3094,16 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal"; PRODUCT_MODULE_NAME = Monal; PRODUCT_NAME = Monal; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3097,6 +3158,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3128,8 +3190,12 @@ MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.0; ONLY_ACTIVE_ARCH = YES; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = NO; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -3156,6 +3222,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = dwarf; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; @@ -3173,6 +3240,8 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -3216,6 +3285,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3240,6 +3310,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; @@ -3285,6 +3357,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3309,6 +3382,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; @@ -3357,9 +3432,12 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -3387,8 +3465,12 @@ LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.0; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = NO; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; @@ -3422,9 +3504,11 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = Monal.macos.entitlements; COMPILER_INDEX_STORE_ENABLE = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; + ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -3449,12 +3533,16 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal"; PRODUCT_MODULE_NAME = Monal; PRODUCT_NAME = Monal; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3473,6 +3561,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = shareSheet.entitlements; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; @@ -3484,6 +3573,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.shareSheet; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.shareSheet"; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3502,6 +3593,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/NotificationService.macos.entitlements; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -3513,6 +3605,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.monal.notificationService"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3528,6 +3622,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -3546,6 +3641,9 @@ PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3563,12 +3661,15 @@ baseConfigurationReference = 7D6715099247A9CCC180EE30 /* Pods-MonalUITests.beta.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = MonalUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; TEST_TARGET_NAME = Monal; VALIDATE_PRODUCT = YES; }; @@ -3609,6 +3710,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3633,6 +3735,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; @@ -3681,9 +3785,12 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -3711,7 +3818,11 @@ LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.0; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = NO; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = IS_QUICKSY; SWIFT_VERSION = 5.0; @@ -3747,10 +3858,12 @@ CODE_SIGN_STYLE = Automatic; COMPILER_INDEX_STORE_ENABLE = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; + ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", @@ -3775,6 +3888,7 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = "org.monal-im.prod.ios.quicksy"; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.quicksy"; PRODUCT_MODULE_NAME = Monal; @@ -3782,6 +3896,9 @@ PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3800,6 +3917,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Quicksy.sharesheet.entitlements; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; @@ -3811,6 +3929,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "org.monal-im.prod.ios.quicksy.shareSheet"; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.quicksy.shareSheet"; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3829,6 +3949,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/Quicksy.NotificationService.macos.entitlements; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = NotificationService/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -3840,6 +3961,8 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = "org.monal-im.prod.catalyst.quicksy.notificationService"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3855,6 +3978,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -3873,6 +3997,9 @@ PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -3890,12 +4017,15 @@ baseConfigurationReference = 26ABE9FB494A9E7F3044C695 /* Pods-MonalUITests.appstore-quicksy.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = MonalUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; TEST_TARGET_NAME = Monal; VALIDATE_PRODUCT = YES; }; @@ -3936,6 +4066,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3960,6 +4091,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; @@ -3975,12 +4108,15 @@ baseConfigurationReference = F7506FDE7A78EB0CAB14FF60 /* Pods-MonalUITests.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = MonalUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; TEST_TARGET_NAME = Monal; }; name = Debug; @@ -3990,12 +4126,15 @@ baseConfigurationReference = 39B989B9775C0725A810D271 /* Pods-MonalUITests.adhoc.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = MonalUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; TEST_TARGET_NAME = Monal; VALIDATE_PRODUCT = YES; }; @@ -4006,12 +4145,15 @@ baseConfigurationReference = D8D2595B2BE453296E59F1AF /* Pods-MonalUITests.appstore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = MonalUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; TEST_TARGET_NAME = Monal; VALIDATE_PRODUCT = YES; }; @@ -4055,9 +4197,12 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = S8D843U34Y; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_DYNAMIC_NO_PIC = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -4087,8 +4232,12 @@ LLVM_LTO = NO; MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 6.2.0; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = NO; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "IS_ALPHA DEBUG"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -4123,6 +4272,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = Alpha.Monal.macos.entitlements; COMPILER_INDEX_STORE_ENABLE = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; ENABLE_PREVIEWS = YES; @@ -4152,11 +4302,15 @@ ); LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; + PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; PRODUCT_BUNDLE_IDENTIFIER = monal.alpha; PRODUCT_MODULE_NAME = Monal; PRODUCT_NAME = Monal.alpha; PROVISIONING_PROFILE = ""; "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -4176,6 +4330,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = Alpha.shareSheet.entitlements; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; @@ -4186,6 +4341,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = monal.alpha.shareSheet; PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -4204,6 +4361,7 @@ "CODE_SIGN_ENTITLEMENTS[sdk=macosx*]" = NotificationService/Alpha.NotificationService.macos.entitlements; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = NotificationService/Alpha.Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -4214,6 +4372,8 @@ PRODUCT_BUNDLE_IDENTIFIER = monal.alpha.NotificaionService; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -4229,6 +4389,7 @@ ALWAYS_SEARCH_USER_PATHS = NO; APPLICATION_EXTENSION_API_ONLY = YES; CURRENT_PROJECT_VERSION = 0; + DEAD_CODE_STRIPPING = NO; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -4247,6 +4408,9 @@ PRODUCT_BUNDLE_IDENTIFIER = im.Monal.monalxmpp; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_STYLE = "non-global"; + STRIP_SWIFT_SYMBOLS = NO; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -4264,12 +4428,15 @@ baseConfigurationReference = 5BACDACCFE405FE0C903C897 /* Pods-MonalUITests.alpha.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = MonalUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; TEST_TARGET_NAME = Monal; VALIDATE_PRODUCT = YES; }; @@ -4310,6 +4477,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4334,6 +4502,8 @@ PRODUCT_BUNDLE_IDENTIFIER = G7YU7X7KRJ.SworIM.MonalXMPPUnitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + STRIP_INSTALLED_PRODUCT = NO; + STRIP_SWIFT_SYMBOLS = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "MonalXMPPUnitTests/MonalXMPPUnitTests-Bridging-Header.h"; From 8a734fb52e90afafbd4936098aee51828ad3d935 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 27 Feb 2024 23:28:18 +0100 Subject: [PATCH 080/115] Fix crashes for dummy contacts havind lastInteractionTime == NSNull --- Monal/Classes/DataLayer.m | 2 +- Monal/Classes/MLContact.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index 8508c042c9..68b867c8a5 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -478,7 +478,7 @@ -(NSDictionary* _Nullable) contactDictionaryForUsername:(NSString*) username for { NSMutableDictionary* contact = [results[0] mutableCopy]; //correctly extract NSDate object or 1970, if last interaction is zero - contact[@"lastInteraction"] = [self lastInteractionOfJid:username forAccountNo:accountNo]; + contact[@"lastInteraction"] = nilWrapper([self lastInteractionOfJid:username forAccountNo:accountNo]); //if we have this muc in our favorites table, this muc is "subscribed" if([self.db executeScalar:@"SELECT room FROM muc_favorites WHERE room=? AND account_id=?;" andArguments:@[username, accountNo]] != nil) contact[@"subscription"] = @"both"; diff --git a/Monal/Classes/MLContact.m b/Monal/Classes/MLContact.m index 2343a25628..ed8ccaedf7 100644 --- a/Monal/Classes/MLContact.m +++ b/Monal/Classes/MLContact.m @@ -745,7 +745,7 @@ +(MLContact*) contactFromDictionary:(NSDictionary*) dic contact.isEncrypted = [[dic objectForKey:@"encrypt"] boolValue]; contact.isMuted = [[dic objectForKey:@"muted"] boolValue]; // initial value comes from db, all other values get updated by our kMonalLastInteractionUpdatedNotice handler - contact.lastInteractionTime = [dic objectForKey:@"lastInteraction"]; //no default needed, already done in DataLayer + contact.lastInteractionTime = nilExtractor([dic objectForKey:@"lastInteraction"]); //no default needed, already done in DataLayer contact->_avatar = nil; return contact; } From 453687c4e8dc9b02437df77b3483ddec0189c05a Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 27 Feb 2024 23:47:53 +0100 Subject: [PATCH 081/115] Fix crash on upload encryption failure --- Monal/Classes/MLFiletransfer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/MLFiletransfer.m b/Monal/Classes/MLFiletransfer.m index 9f49d07ba8..c03a24adba 100644 --- a/Monal/Classes/MLFiletransfer.m +++ b/Monal/Classes/MLFiletransfer.m @@ -845,7 +845,7 @@ +(void) setErrorType:(NSString*) errorType andErrorText:(NSString*) errorText fo { DDLogInfo(@"Encrypting file data before upload"); encryptedPayload = [AESGcm encrypt:fileData keySize:32]; - if(encryptedPayload) + if(encryptedPayload && encryptedPayload.body != nil) { NSMutableData* encryptedData = [encryptedPayload.body mutableCopy]; [encryptedData appendData:encryptedPayload.authTag]; From bea7f4f1f7adc2ff77e0ad87c9569c86d0f126ec Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 00:52:10 +0100 Subject: [PATCH 082/115] Tag log messages of xml parser and MLStream with corresponding xmpp account --- Monal/Classes/MLStream.h | 2 +- Monal/Classes/MLStream.m | 26 ++++++++++++++------------ Monal/Classes/xmpp.m | 28 +++++++++++++++------------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Monal/Classes/MLStream.h b/Monal/Classes/MLStream.h index 906fe9ef2d..60e07b59d4 100644 --- a/Monal/Classes/MLStream.h +++ b/Monal/Classes/MLStream.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nullable, readonly, copy) NSError* streamError; -+(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port tls:(BOOL) tls inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream; ++(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port tls:(BOOL) tls inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream logtag:(id _Nullable) logtag; -(void) startTLS; @property(readonly) BOOL hasTLS; @property(readonly) BOOL isTLS13; diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index 07b974ddf5..e65af8e0d6 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -414,7 +414,7 @@ -(void) generateEvent:(NSStreamEvent) event @implementation MLStream -+(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port tls:(BOOL) tls inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream ++(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host connectPort:(NSNumber*) port tls:(BOOL) tls inputStream:(NSInputStream* _Nullable * _Nonnull) inputStream outputStream:(NSOutputStream* _Nullable * _Nonnull) outputStream logtag:(id _Nullable) logtag { //create state volatile __block BOOL wasOpenOnce = NO; @@ -463,9 +463,9 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host nw_protocol_definition_t starttls_framer_definition = nw_framer_create_definition([[[NSUUID UUID] UUIDString] UTF8String], NW_FRAMER_CREATE_FLAGS_DEFAULT, ^(nw_framer_t framer) { //we don't need any locking for our counter because all framers will be started in the same internal network queue int framerId = startupCounter++; - DDLogInfo(@"Framer(%d) %@ start called with wasOpenOnce=%@...", framerId, framer, bool2str(wasOpenOnce)); + DDLogInfo(@"%@: Framer(%d) %@ start called with wasOpenOnce=%@...", logtag, framerId, framer, bool2str(wasOpenOnce)); nw_framer_set_stop_handler(framer, (nw_framer_stop_handler_t)^(nw_framer_t _Nullable framer) { - DDLogInfo(@"Framer(%d) stop called: %@", framerId, framer); + DDLogInfo(@"%@, Framer(%d) stop called: %@", logtag, framerId, framer); return YES; }); @@ -477,6 +477,7 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) { nw_framer_parse_input(framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { MLAssert(NO, @"Unexpected incoming bytes in first framer!", (@{ + @"logtag": nilWrapper(logtag), @"framer": framer, @"buffer": [NSData dataWithBytes:buffer length:buffer_length], @"buffer_length": @(buffer_length), @@ -488,6 +489,7 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host }); nw_framer_set_output_handler(framer, ^(nw_framer_t framer, nw_framer_message_t message, size_t message_length, bool is_complete) { MLAssert(NO, @"Unexpected outgoing bytes in first framer!", (@{ + @"logtag": nilWrapper(logtag), @"framer": framer, @"message": message, @"message_length": @(message_length), @@ -522,7 +524,7 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host shared_state.framer = framer; return nw_framer_start_result_will_mark_ready; }); - DDLogInfo(@"Not doing direct TLS: appending framer to protocol stack..."); + DDLogInfo(@"%@: Not doing direct TLS: appending framer to protocol stack...", logtag); nw_protocol_stack_prepend_application_protocol(nw_parameters_copy_default_protocol_stack(parameters), nw_framer_create_options(starttls_framer_definition)); } //needed to activate tcp fast open with apple's internal tls framer @@ -531,7 +533,7 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host //create and configure connection object nw_endpoint_t endpoint = nw_endpoint_create_host([host cStringUsingEncoding:NSUTF8StringEncoding], [[port stringValue] cStringUsingEncoding:NSUTF8StringEncoding]); nw_connection_t connection = nw_connection_create(endpoint, parameters); - nw_connection_set_queue(connection, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); + nw_connection_set_queue(connection, dispatch_queue_create_with_target([NSString stringWithFormat:@"im.monal.networking:%@", logtag].UTF8String, DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0))); //configure shared state shared_state.connection = connection; @@ -543,7 +545,7 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host //connection was opened once (e.g. opening=YES) and closed later on (e.g. open=NO) if(wasOpenOnce && !shared_state.open) { - DDLogVerbose(@"ignoring call to nw_connection state_changed_handler, connection already closed: %@ --> %du, %@", self, state, error); + DDLogVerbose(@"%@: ignoring call to nw_connection state_changed_handler, connection already closed: %@ --> %du, %@", logtag, self, state, error); return; } } @@ -553,11 +555,11 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host //which seems to mean: if the network path changed (for example connectivity regained) //if this happens inside the connection timeout all is ok //if not, the connection will be cancelled already and everything will be ok, too - DDLogVerbose(@"got nw_connection_state_waiting and ignoring it, see comments in code..."); + DDLogVerbose(@"%@: got nw_connection_state_waiting and ignoring it, see comments in code...", logtag); } else if(state == nw_connection_state_failed) { - DDLogError(@"Connection failed"); + DDLogError(@"%@: Connection failed", logtag); NSError* st_error = (NSError*)CFBridgingRelease(nw_error_copy_cf_error(error)); @synchronized(shared_state) { shared_state.error = st_error; @@ -567,7 +569,7 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host } else if(state == nw_connection_state_ready) { - DDLogInfo(@"Connection established, wasOpenOnce: %@", bool2str(wasOpenOnce)); + DDLogInfo(@"%@: Connection established, wasOpenOnce: %@", bool2str(wasOpenOnce), logtag); if(!wasOpenOnce) { wasOpenOnce = YES; @@ -601,17 +603,17 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host else if(state == nw_connection_state_cancelled) { //ignore this (we use reference counting) - DDLogVerbose(@"ignoring call to nw_connection state_changed_handler with state nw_connection_state_cancelled: %@ (%@)", self, error); + DDLogVerbose(@"%@: ignoring call to nw_connection state_changed_handler with state nw_connection_state_cancelled: %@ (%@)", logtag, self, error); } else if(state == nw_connection_state_invalid) { //ignore all other states (preparing, invalid) - DDLogVerbose(@"ignoring call to nw_connection state_changed_handler with state nw_connection_state_invalid: %@ (%@)", self, error); + DDLogVerbose(@"%@: ignoring call to nw_connection state_changed_handler with state nw_connection_state_invalid: %@ (%@)", logtag, self, error); } else if(state == nw_connection_state_preparing) { //ignore all other states (preparing, invalid) - DDLogVerbose(@"ignoring call to nw_connection state_changed_handler with state nw_connection_state_preparing: %@ (%@)", self, error); + DDLogVerbose(@"%@: ignoring call to nw_connection state_changed_handler with state nw_connection_state_preparing: %@ (%@)", logtag, self, error); } else unreachable(); diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 4a5285236c..361eb437c0 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -114,6 +114,7 @@ @interface xmpp() BOOL _lastIdleState; NSMutableDictionary* _mamPageArrays; NSString* _internalID; + NSString* _logtag; NSMutableDictionary* _inCatchup; //registration related stuff @@ -180,7 +181,8 @@ -(id) initWithServer:(nonnull MLXMPPServer*) server andIdentity:(nonnull MLXMPPI self = [super init]; u_int32_t i = arc4random(); _internalID = [HelperTools hexadecimalString:[NSData dataWithBytes: &i length: sizeof(i)]]; - DDLogVerbose(@"Created account %@ with id %@", accountNo, _internalID); + _logtag = [NSString stringWithFormat:@"[%@:%@]", accountNo, _internalID]; + DDLogVerbose(@"Creating account %@ with id %@", accountNo, _internalID); self.accountNo = accountNo; self.connectionProperties = [[MLXMPPConnection alloc] initWithServer:server andIdentity:identity]; @@ -557,12 +559,12 @@ -(void) createStreams if(self.connectionProperties.server.isDirectTLS == YES) { DDLogInfo(@"creating directTLS streams"); - [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort tls:YES inputStream:&localIStream outputStream:&localOStream]; + [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort tls:YES inputStream:&localIStream outputStream:&localOStream logtag:self->_logtag]; } else { DDLogInfo(@"creating plaintext streams"); - [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort tls:NO inputStream:&localIStream outputStream:&localOStream]; + [MLStream connectWithSNIDomain:self.connectionProperties.identity.domain connectHost:self.connectionProperties.server.connectServer connectPort:self.connectionProperties.server.connectPort tls:NO inputStream:&localIStream outputStream:&localOStream logtag:self->_logtag]; } if(localOStream) @@ -1242,16 +1244,16 @@ -(void) prepareXMPPParser BOOL appex = [HelperTools isAppExtension]; if(_xmlParser!=nil) { - DDLogInfo(@"resetting old xml parser"); + DDLogInfo(@"%@: resetting old xml parser", self->_logtag); [_xmlParser setDelegate:nil]; [_xmlParser abortParsing]; [_parseQueue cancelAllOperations]; //throw away all parsed but not processed stanzas (we aborted the parser right now) } if(!_baseParserDelegate) { - DDLogInfo(@"creating parser delegate"); + DDLogInfo(@"%@: creating parser delegate", self->_logtag); _baseParserDelegate = [[MLBasePaser alloc] initWithCompletion:^(MLXMLNode* _Nullable parsedStanza) { - DDLogVerbose(@"Parse finished for new <%@> stanza...", parsedStanza.element); + DDLogVerbose(@"%@: Parse finished for new <%@> stanza...", self->_logtag, parsedStanza.element); //don't parse any more if we reached > 50 stanzas already parsed and waiting in parse queue //this makes ure we don't need to much memory while parsing a flood of stanzas and, in theory, @@ -1267,16 +1269,16 @@ -(void) prepareXMPPParser break; double waittime = (double)[self->_parseQueue operationCount] / 100.0; - DDLogInfo(@"Sleeping %f seconds because parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", waittime, (unsigned long)[self->_parseQueue operationCount], usedMemory, (CGFloat)os_proc_available_memory() / 1048576); + DDLogInfo(@"%@: Sleeping %f seconds because parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", self->_logtag, waittime, (unsigned long)[self->_parseQueue operationCount], usedMemory, (CGFloat)os_proc_available_memory() / 1048576); [NSThread sleepForTimeInterval:waittime]; wasSleeping = YES; } if(wasSleeping) - DDLogInfo(@"Sleeping has ended, parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", (unsigned long)[self->_parseQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); + DDLogInfo(@"%@: Sleeping has ended, parse queue has %lu entries and used/available memory: %.3fMiB / %.3fMiB...", self->_logtag, (unsigned long)[self->_parseQueue operationCount], [HelperTools report_memory], (CGFloat)os_proc_available_memory() / 1048576); if(self.accountState < kStateConnected) { - DDLogWarn(@"Throwing away incoming stanza *before* queueing in parse queue, accountState < kStateConnected"); + DDLogWarn(@"%@: Throwing away incoming stanza *before* queueing in parse queue, accountState < kStateConnected", self->_logtag); return; } @@ -1348,7 +1350,7 @@ -(void) prepareXMPPParser } else { - DDLogInfo(@"resetting parser delegate"); + DDLogInfo(@"%@: resetting parser delegate", self->_logtag); [_baseParserDelegate reset]; } @@ -1361,10 +1363,10 @@ -(void) prepareXMPPParser [_xmlParser setDelegate:_baseParserDelegate]; // do the stanza parsing in the low priority (=utility) global queue - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ - DDLogInfo(@"calling parse"); + dispatch_async(dispatch_queue_create_with_target([NSString stringWithFormat:@"im.monal.xmlparser%@", self->_logtag].UTF8String, DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)), ^{ + DDLogInfo(@"%@: calling parse", self->_logtag); [self->_xmlParser parse]; //blocking operation - DDLogInfo(@"parse ended"); + DDLogInfo(@"%@: parse ended", self->_logtag); }); } From 84b0e24b308b4dd237da3fd5cb52da6267d18077 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 02:02:47 +0100 Subject: [PATCH 083/115] Fix spurious "new omemo device detected" notificaions, thanks steve These notifications are a result of a rare race condition (or broken servers): if a server has to query our disco because it doesn't know our disco hash, that can take more time than our mam catchup, if the catchup involves only one single RSM mam page: the server will learn about our +notify and send out a headline push of our own devicelist *after* sending the mam response iq. If by the time our mam catchup is finished, we did not receive our own devicelist, we assume an empty devicelist and publish a new devicelist node containing only our own deviceid). This commit fixes this by forefully fetching our own devicelist if we did not yet receive it once the catchup is done. If our device is not listed in this devicelist node, that fetch and the headline push eventually coming in may both trigger a devicelist publish, but that should not do any harm. --- Monal/Classes/MLOMEMO.m | 105 +++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 50f4999ed5..781a55f5b4 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -195,51 +195,68 @@ -(void) handleCatchupDone:(NSNotification*) notification self.state.catchupDone = YES; //if we did not see our own devicelist until now that means the server does not have any devicelist stored - //(e.g. we are the first omemo capable client) - //--> publish devicelist by faking an empty server-sent devicelist - //self.state.hasSeenDeviceList will be set to YES once the published devicelist gets returned to us by a pubsub headline echo - //(e.g. once the devicelist was safely stored on our server) + //OR: our own devicelist could have been delayed by the server having to do a disco query to us to discover our +notify + //for the devicelist (e.g. either we are the first omemo capable client, or the devicelist has just been delayed) + //--> forcefully fetch devicelist to be sure (but don't subscribe, we are +notify and have a presence subscription to our own account) + //If our device is not listed in this devicelist node, that fetch and the headline push eventually coming in + //may both trigger a devicelist publish, but that should not do any harm if(self.state.hasSeenDeviceList == NO) { - DDLogInfo(@"We did not see any devicelist during catchup since last non-smacks-resume reconnect, adding our device to an otherwise empty devicelist and publishing this list..."); - [self processOMEMODevices:[NSSet new] from:self.account.connectionProperties.identity.jid]; + DDLogInfo(@"We did not see any devicelist during catchup since last non-smacks-resume reconnect, forcefully fetching own devicelist..."); + [self queryOMEMODevices:self.account.connectionProperties.identity.jid withSubscribe:NO]; } else { - //generate new prekeys if needed and publish them - [self generateNewKeysIfNeeded]; + [self generateNewKeysIfNeeded]; //generate new prekeys if needed and publish them + [self repairQueuedSessions]; } - - //send all needed key transport elements now (added by incoming catchup messages or bundle fetches) - //the queue is needed to make sure we won't send multiple key transport messages to a single contact/device - //only because we received multiple messages from this user in the catchup or fetched multiple bundles - //queuedKeyTransportElements will survive any smacks or non-smacks resumptions and eventually trigger key transport elements - //once the catchup could be finished (could take several smacks resumptions to finish the whole (mam) catchup) - //has to be synchronized because [xmpp sendMessage:] could be called from main thread - @synchronized(self.state.queuedKeyTransportElements) { - DDLogDebug(@"Replaying queuedKeyTransportElements for all jids: %@", self.state.queuedKeyTransportElements); - for(NSString* jid in [self.state.queuedKeyTransportElements allKeys]) - [self retriggerKeyTransportElementsForJid:jid]; - } - - //handle all broken sessions now (e.g. reestablish them by fetching their bundles and sending key transport elements afterwards) - //the code handling the fetched bundle will check for an entry in queuedSessionRepairs and send - //a key transport element if such an entry can be found - //it removes the entry in queuedSessionRepairs afterwards, so no need to remove it here - //queuedSessionRepairs will survive a non-smacks relogin and trigger these dropped bundle fetches again to complete them - //has to be synchronized because [xmpp sendMessage:] could be called from main thread - @synchronized(self.state.queuedSessionRepairs) { - DDLogDebug(@"Replaying queuedSessionRepairs: %@", self.state.queuedSessionRepairs); - for(NSString* jid in self.state.queuedSessionRepairs) - for(NSNumber* rid in self.state.queuedSessionRepairs[jid]) - [self queryOMEMOBundleFrom:jid andDevice:rid]; - } - - DDLogVerbose(@"New state: %@", self.state); } #endif } +-(void) handleOwnDevicelistFetchError +{ + //devicelist could neither be fetched explicitly nor by using +notify --> publish own devicelist by faking an empty server-sent devicelist + //self.state.hasSeenDeviceList will be set to YES once the published devicelist gets returned to us by a pubsub headline echo + //(e.g. once the devicelist was safely stored on our server) + DDLogInfo(@"Could not fetch own devicelist, faking empty devicelist to publish our own deviceid..."); + [self processOMEMODevices:[NSSet new] from:self.account.connectionProperties.identity.jid]; + + [self repairQueuedSessions]; +} + +-(void) repairQueuedSessions +{ + DDLogInfo(@"Own devicelist was handled, now trying to repair queued sessions..."); + + //send all needed key transport elements now (added by incoming catchup messages or bundle fetches) + //the queue is needed to make sure we won't send multiple key transport messages to a single contact/device + //only because we received multiple messages from this user in the catchup or fetched multiple bundles + //queuedKeyTransportElements will survive any smacks or non-smacks resumptions and eventually trigger key transport elements + //once the catchup could be finished (could take several smacks resumptions to finish the whole (mam) catchup) + //has to be synchronized because [xmpp sendMessage:] could be called from main thread + @synchronized(self.state.queuedKeyTransportElements) { + DDLogDebug(@"Replaying queuedKeyTransportElements for all jids: %@", self.state.queuedKeyTransportElements); + for(NSString* jid in [self.state.queuedKeyTransportElements allKeys]) + [self retriggerKeyTransportElementsForJid:jid]; + } + + //handle all broken sessions now (e.g. reestablish them by fetching their bundles and sending key transport elements afterwards) + //the code handling the fetched bundle will check for an entry in queuedSessionRepairs and send + //a key transport element if such an entry can be found + //it removes the entry in queuedSessionRepairs afterwards, so no need to remove it here + //queuedSessionRepairs will survive a non-smacks relogin and trigger these dropped bundle fetches again to complete them + //has to be synchronized because [xmpp sendMessage:] could be called from main thread + @synchronized(self.state.queuedSessionRepairs) { + DDLogDebug(@"Replaying queuedSessionRepairs: %@", self.state.queuedSessionRepairs); + for(NSString* jid in self.state.queuedSessionRepairs) + for(NSNumber* rid in self.state.queuedSessionRepairs[jid]) + [self queryOMEMOBundleFrom:jid andDevice:rid]; + } + + DDLogVerbose(@"New state: %@", self.state); +} + -(void) retriggerKeyTransportElementsForJid:(NSString*) jid { //send all needed key transport elements now (added by incoming catchup messages or bundle fetches) @@ -294,7 +311,7 @@ -(void) retriggerKeyTransportElementsForJid:(NSString*) jid } $$ --(void) queryOMEMODevices:(NSString*) jid +-(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe { //don't fetch devicelist twice (could be triggered by multiple useractions in a row) if([self.state.openDevicelistFetches containsObject:jid]) @@ -302,7 +319,7 @@ -(void) queryOMEMODevices:(NSString*) jid else { //fetch newest devicelist (this is needed even after a subscribe on at least prosody) - [self.account.pubsub fetchNode:@"eu.siacs.conversations.axolotl.devicelist" from:jid withItemsList:nil andHandler:$newHandlerWithInvalidation(self, handleDevicelistFetch, handleDevicelistFetchInvalidation, $BOOL(subscribe, YES))]; + [self.account.pubsub fetchNode:@"eu.siacs.conversations.axolotl.devicelist" from:jid withItemsList:nil andHandler:$newHandlerWithInvalidation(self, handleDevicelistFetch, handleDevicelistFetchInvalidation, $BOOL(subscribe))]; } } @@ -328,11 +345,14 @@ -(void) queryOMEMODevices:(NSString*) jid //mark devicelist fetch as done [self.state.openDevicelistFetches removeObject:jid]; + [self handleOwnDevicelistFetchError]; + //retrigger queued key transport elements for this jid (if any) [self retriggerKeyTransportElementsForJid:jid]; $$ $$instance_handler(handleDevicelistFetch, account.omemo, $$ID(xmpp*, account), $$BOOL(subscribe), $$ID(NSString*, jid), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason), $_ID((NSDictionary*), data)) + //mark devicelist fetch as done [self.state.openDevicelistFetches removeObject:jid]; if(success == NO) @@ -341,11 +361,16 @@ -(void) queryOMEMODevices:(NSString*) jid DDLogError(@"Error while fetching omemo devices: jid: %@ - %@", jid, errorIq); else DDLogError(@"Error while fetching omemo devices: jid: %@ - %@", jid, errorReason); - // TODO: improve error handling + if([self.account.connectionProperties.identity.jid isEqualToString:jid]) + [self handleOwnDevicelistFetchError]; + else + { + // TODO: improve error handling + } } else { - if(subscribe) + if(subscribe && ![self.account.connectionProperties.identity.jid isEqualToString:jid]) { DDLogInfo(@"Successfully fetched devicelist, now subscribing to this node for updates..."); //don't subscribe devicelist twice (could be triggered by multiple useractions in a row) @@ -1242,7 +1267,7 @@ -(void) subscribeAndFetchDevicelistIfNoSessionExistsForJid:(NSString*) buddyJid MLContact* contact = [MLContact createContactFromJid:buddyJid andAccountNo:self.account.accountNo]; //only do so if we don't receive automatic headline pushes of the devicelist if(!contact.isSubscribedTo) - [self queryOMEMODevices:buddyJid]; + [self queryOMEMODevices:buddyJid withSubscribe:YES]; } } From a315945b82b3ff0cd322aa9e743316fd0971610c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 02:51:35 +0100 Subject: [PATCH 084/115] Also name several other threads --- Monal/Classes/HelperTools.m | 7 ++++--- Monal/Classes/MLXMPPManager.m | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index a966ec2172..e920228f8b 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -471,9 +471,10 @@ +(NSRunLoop*) getExtraRunloopWithIdentifier:(MLRunLoopIdentifier) identifier //every identifier has its own thread priority/qos class __block dispatch_queue_priority_t priority; + __block char* name; switch(identifier) { - case MLRunLoopIdentifierNetwork: priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND; break; + case MLRunLoopIdentifierNetwork: priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND; name = "im.monal.runloop.networking"; break; default: unreachable(@"unknown runloop identifier!"); } @@ -482,7 +483,7 @@ +(NSRunLoop*) getExtraRunloopWithIdentifier:(MLRunLoopIdentifier) identifier { NSCondition* condition = [NSCondition new]; [condition lock]; - dispatch_async(dispatch_get_global_queue(priority, 0), ^{ + dispatch_async(dispatch_queue_create_with_target(name, DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(priority, 0)), ^{ //we don't need an @synchronized block around this because the @synchronized block of the outer thread //waits until we signal our condition (e.g. no other thread can race with us) NSRunLoop* localLoop = runloops[@(identifier)] = [NSRunLoop currentRunLoop]; @@ -1558,7 +1559,7 @@ +(void) activityLog #endif if(log_activity) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + dispatch_async(dispatch_queue_create_with_target("im.monal.activityLog", DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)), ^{ unsigned long counter = 1; while(counter++) { diff --git a/Monal/Classes/MLXMPPManager.m b/Monal/Classes/MLXMPPManager.m index 0601068739..440c02d9db 100644 --- a/Monal/Classes/MLXMPPManager.m +++ b/Monal/Classes/MLXMPPManager.m @@ -319,7 +319,7 @@ -(id) init //trigger iq invalidations from a background thread because timeouts aren't time critical //we use this to decrement the timeout value of an iq handler every second until it reaches zero - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + dispatch_async(dispatch_queue_create_with_target("im.monal.iqtimeouts", DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)), ^{ while(YES) { for(xmpp* account in [MLXMPPManager sharedInstance].connectedXMPP) [account updateIqHandlerTimeouts]; From cd8501af56d2340f61ce348a33c2b023d59588ab Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 20:42:03 +0100 Subject: [PATCH 085/115] Assert on nil values in internalTmpFileUploadHandler --- Monal/Classes/MLFiletransfer.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Monal/Classes/MLFiletransfer.m b/Monal/Classes/MLFiletransfer.m index c03a24adba..6527f8ee0e 100644 --- a/Monal/Classes/MLFiletransfer.m +++ b/Monal/Classes/MLFiletransfer.m @@ -866,6 +866,10 @@ +(void) setErrorType:(NSString*) errorType andErrorText:(NSString*) errorText fo if(encrypted) sendMimeType = @"application/octet-stream"; + MLAssert(fileData != nil, @"fileData should never be nil!"); + MLAssert(userFacingFilename != nil, @"userFacingFilename should never be nil!"); + MLAssert(sendMimeType != nil, @"sendMimeType should never be nil!"); + DDLogDebug(@"Requesting file upload slot for mimeType %@", sendMimeType); [account requestHTTPSlotWithParams:@{ @"data":fileData, From abb356fbb97a857d8ee89a52474f21ab90ab1dfd Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 21:38:23 +0100 Subject: [PATCH 086/115] Fix caps hash node to use https://monal-im.org/ --- Monal/Classes/XMPPPresence.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monal/Classes/XMPPPresence.m b/Monal/Classes/XMPPPresence.m index 5659399084..7ad0c9b7b7 100644 --- a/Monal/Classes/XMPPPresence.m +++ b/Monal/Classes/XMPPPresence.m @@ -31,7 +31,7 @@ -(id) initWithHash:(NSString*) version { self = [self init]; [self addChildNode:[[MLXMLNode alloc] initWithElement:@"c" andNamespace:@"http://jabber.org/protocol/caps" withAttributes:@{ - @"node": @"http://monal-im.org/", + @"node": @"https://monal-im.org/", @"hash": @"sha-1", @"ver": version } andChildren:@[] andData:nil]]; From 85321f47c8cb41e095d8e8c6eb3e758a004e2d20 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 22:25:28 +0100 Subject: [PATCH 087/115] Privacy: don't leak caps hash knowledge across accounts --- Monal/Classes/ContactResources.swift | 2 +- Monal/Classes/DataLayer.h | 4 +- Monal/Classes/DataLayer.m | 73 ++++++++-------------------- Monal/Classes/DataLayerMigrations.m | 16 +++++- Monal/Classes/MLIQProcessor.m | 3 +- Monal/Classes/xmpp.h | 2 - Monal/Classes/xmpp.m | 31 ++---------- 7 files changed, 43 insertions(+), 88 deletions(-) diff --git a/Monal/Classes/ContactResources.swift b/Monal/Classes/ContactResources.swift index 50d500edd1..726869ab01 100644 --- a/Monal/Classes/ContactResources.swift +++ b/Monal/Classes/ContactResources.swift @@ -77,7 +77,7 @@ struct ContactResources: View { let capsVer = DataLayer.sharedInstance().getVerForUser(self.contact.contactJid, andResource:resource, onAccountNo:self.contact.accountId) Text("Caps hash: \(String(describing:capsVer))") Divider() - if let capsSet = DataLayer.sharedInstance().getCapsforVer(capsVer) as? Set { + if let capsSet = DataLayer.sharedInstance().getCapsforVer(capsVer, onAccountNo:contact.obj.accountId) as? Set { let caps = Array(capsSet) VStack(alignment: .leading) { ForEach(caps, id: \.self) { cap in diff --git a/Monal/Classes/DataLayer.h b/Monal/Classes/DataLayer.h index 3d3b93b48e..0d7dc3cf89 100644 --- a/Monal/Classes/DataLayer.h +++ b/Monal/Classes/DataLayer.h @@ -76,8 +76,8 @@ extern NSString* const kMessageTypeFiletransfer; -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user andResource:(NSString*) resource onAccountNo:(NSNumber*) accountNo; -(NSString*) getVerForUser:(NSString*) user andResource:(NSString*) resource onAccountNo:(NSNumber*) accountNo; -(void) setVer:(NSString*) ver forUser:(NSString*) user andResource:(NSString*) resource onAccountNo:(NSNumber*) accountNo; --(NSSet* _Nullable) getCapsforVer:(NSString*) ver; --(void) setCaps:(NSSet*) caps forVer:(NSString*) ver; +-(NSSet* _Nullable) getCapsforVer:(NSString*) ver onAccountNo:(NSNumber*) accountNo; +-(void) setCaps:(NSSet*) caps forVer:(NSString*) ver onAccountNo:(NSNumber*) accountNo; #pragma mark presence functions -(void) setResourceOnline:(XMPPPresence*) presenceObj forAccount:(NSNumber*) accountNo; diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index 68b867c8a5..24ca7c97fa 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -535,8 +535,8 @@ -(NSDictionary* _Nullable) contactDictionaryForUsername:(NSString*) username for -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user onAccountNo:(NSNumber*) accountNo { return [self.db boolReadTransaction:^{ - NSString* query = @"SELECT COUNT(*) FROM buddylist AS a INNER JOIN buddy_resources AS b ON a.buddy_id=b.buddy_id INNER JOIN ver_info AS c ON b.ver=c.ver WHERE buddy_name=? AND account_id=? AND cap=?;"; - NSArray *params = @[user, accountNo, cap]; + NSString* query = @"SELECT COUNT(*) FROM buddylist AS a INNER JOIN buddy_resources AS b ON a.buddy_id=b.buddy_id INNER JOIN ver_info AS c ON b.ver=c.ver WHERE a.buddy_name=? AND a.account_id=? AND c.cap=? AND c.account_id=?;"; + NSArray *params = @[user, accountNo, cap, accountNo]; NSNumber* count = (NSNumber*) [self.db executeScalar:query andArguments:params]; return (BOOL)([count integerValue]>0); }]; @@ -545,8 +545,8 @@ -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user onAccountNo:(NSNumber* -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user andResource:(NSString*) resource onAccountNo:(NSNumber*) accountNo { return [self.db boolReadTransaction:^{ - NSString* query = @"SELECT COUNT(*) FROM buddylist AS a INNER JOIN buddy_resources AS b ON a.buddy_id=b.buddy_id INNER JOIN ver_info AS c ON b.ver=c.ver WHERE buddy_name=? AND resource=? AND account_id=? AND cap=?;"; - NSNumber* count = (NSNumber*) [self.db executeScalar:query andArguments:@[user, resource, accountNo, cap]]; + NSString* query = @"SELECT COUNT(*) FROM buddylist AS a INNER JOIN buddy_resources AS b ON a.buddy_id=b.buddy_id INNER JOIN ver_info AS c ON b.ver=c.ver WHERE a.buddy_name=? AND b.resource=? AND a.account_id=? AND c.cap=? AND c.account_id=?;"; + NSNumber* count = (NSNumber*) [self.db executeScalar:query andArguments:@[user, resource, accountNo, cap, accountNo]]; return (BOOL)([count integerValue]>0); }]; } @@ -571,70 +571,37 @@ -(void) setVer:(NSString*) ver forUser:(NSString*) user andResource:(NSString*) [self.db executeNonQuery:query andArguments:params]; //update timestamp for this ver string to make it not timeout (old ver strings and features are removed from feature cache after 28 days) - NSString* query2 = @"INSERT INTO ver_timestamp (ver, timestamp) VALUES (?, ?) ON CONFLICT(ver) DO UPDATE SET timestamp=?;"; - NSArray * params2 = @[ver, timestamp, timestamp]; + NSString* query2 = @"UPDATE ver_info SET timestamp=? WHERE ver=? AND account_id=?;"; + NSArray * params2 = @[timestamp, ver, accountNo]; [self.db executeNonQuery:query2 andArguments:params2]; }]; } --(NSSet*) getCapsforVer:(NSString*) ver +-(NSSet*) getCapsforVer:(NSString*) ver onAccountNo:(NSNumber*) accountNo { return [self.db idReadTransaction:^{ - NSString* query = @"select cap from ver_info where ver=?"; - NSArray * params = @[ver]; - NSArray* resultArray = [self.db executeReader:query andArguments:params]; + NSSet* result = [NSSet setWithArray:[self.db executeScalarReader:@"SELECT cap FROM ver_info WHERE ver=? AND account_id=?;" andArguments:@[ver, accountNo]]]; - if(resultArray != nil) - { - DDLogVerbose(@"caps count: %lu", (unsigned long)[resultArray count]); - if([resultArray count] == 0) - return (NSSet*)nil; - NSMutableSet* retval = [NSMutableSet new]; - for(NSDictionary* row in resultArray) - [retval addObject:row[@"cap"]]; - return (NSSet*)retval; - } - else - { - DDLogError(@"caps list is empty"); + DDLogVerbose(@"caps count: %lu", (unsigned long)[result count]); + if([result count] == 0) return (NSSet*)nil; - } + return result; }]; } --(void) setCaps:(NSSet*) caps forVer:(NSString*) ver +-(void) setCaps:(NSSet*) caps forVer:(NSString*) ver onAccountNo:(NSNumber*) accountNo { NSNumber* timestamp = [HelperTools currentTimestampInSeconds]; [self.db voidWriteTransaction:^{ //remove old caps for this ver - NSString* query0 = @"DELETE FROM ver_info WHERE ver=?;"; - NSArray * params0 = @[ver]; - [self.db executeNonQuery:query0 andArguments:params0]; + [self.db executeNonQuery:@"DELETE FROM ver_info WHERE ver=? AND account_id=?;" andArguments:@[ver, accountNo]]; //insert new caps for(NSString* feature in caps) - { - NSString* query1 = @"INSERT INTO ver_info (ver, cap) VALUES (?, ?);"; - NSArray * params1 = @[ver, feature]; - [self.db executeNonQuery:query1 andArguments:params1]; - } - - //update timestamp for this ver string - NSString* query2 = @"INSERT INTO ver_timestamp (ver, timestamp) VALUES (?, ?) ON CONFLICT(ver) DO UPDATE SET timestamp=?;"; - NSArray * params2 = @[ver, timestamp, timestamp]; - [self.db executeNonQuery:query2 andArguments:params2]; - - //cleanup old entries - NSString* query3 = @"SELECT ver FROM ver_timestamp WHERE timestamp" (e.g. lastInteraction > 0) - NSNumber* idle = [self.db executeScalar:@"SELECT R.lastInteraction FROM buddy_resources AS R INNER JOIN buddylist AS B ON R.buddy_id=B.buddy_id INNER JOIN ver_info AS V ON R.ver=V.ver WHERE B.account_id=? AND B.buddy_name=? AND cap='urn:xmpp:idle:1' AND R.lastInteraction!=0 ORDER BY R.lastInteraction DESC LIMIT 1;" andArguments:@[accountNo, jid]]; + NSNumber* idle = [self.db executeScalar:@"SELECT R.lastInteraction FROM buddy_resources AS R INNER JOIN buddylist AS B ON R.buddy_id=B.buddy_id INNER JOIN ver_info AS V ON R.ver=V.ver WHERE B.account_id=? AND B.buddy_name=? AND V.account_id=? AND V.cap='urn:xmpp:idle:1' AND R.lastInteraction!=0 ORDER BY R.lastInteraction DESC LIMIT 1;" andArguments:@[accountNo, jid, accountNo]]; //this will only return a value if the buddy has a last interaction not being NULL or 0 NSNumber* globalIdle = [self.db executeScalar:@"SELECT lastInteraction FROM buddylist WHERE account_id=? AND buddy_name=? AND NOT (lastInteraction IS NULL OR lastInteraction==0);" andArguments:@[accountNo, jid]]; @@ -2352,7 +2319,7 @@ -(NSDate* _Nullable) lastInteractionOfJid:(NSString* _Nonnull) jid andResource:( MLAssert(accountNo != nil, @"accountNo should not be null"); return [self.db idReadTransaction:^{ //this will only return resources supporting "urn:xmpp:idle:1" - NSNumber* lastInteraction = [self.db executeScalar:@"SELECT R.lastInteraction FROM buddy_resources AS R INNER JOIN buddylist AS B ON R.buddy_id=B.buddy_id WHERE B.account_id=? AND B.buddy_name=? AND R.resource=? AND EXISTS(SELECT * FROM ver_info AS V WHERE V.ver=R.ver AND V.cap='urn:xmpp:idle:1') LIMIT 1;" andArguments:@[accountNo, jid, resource]]; + NSNumber* lastInteraction = [self.db executeScalar:@"SELECT R.lastInteraction FROM buddy_resources AS R INNER JOIN buddylist AS B ON R.buddy_id=B.buddy_id WHERE B.account_id=? AND B.buddy_name=? AND R.resource=? AND EXISTS(SELECT * FROM ver_info AS V WHERE V.ver=R.ver AND V.account_id=B.account_id AND V.cap='urn:xmpp:idle:1') LIMIT 1;" andArguments:@[accountNo, jid, resource]]; DDLogDebug(@"LastInteraction of %@/%@ lastInteraction=%@", jid, resource, lastInteraction); if(lastInteraction == nil) return (NSDate*)nil; diff --git a/Monal/Classes/DataLayerMigrations.m b/Monal/Classes/DataLayerMigrations.m index bc4b42512f..80b379c3e9 100644 --- a/Monal/Classes/DataLayerMigrations.m +++ b/Monal/Classes/DataLayerMigrations.m @@ -1020,7 +1020,21 @@ FOREIGN KEY('account_id') REFERENCES 'account'('account_id') ON DELETE CASCADE\ [db executeNonQuery:@"UPDATE account SET username=TRIM(username);"]; [db executeNonQuery:@"UPDATE account SET domain=TRIM(domain);"]; }]; - + + [self updateDB:db withDataLayer:dataLayer toVersion:6.201 withBlock:^{ + [db executeNonQuery:@"DROP TABLE IF EXISTS ver_info;"]; + [db executeNonQuery:@"CREATE TABLE ver_info( \ + ver VARCHAR(32), \ + cap VARCHAR(255), \ + timestamp INTEGER NOT NULL DEFAULT 0, \ + account_id INTEGER NOT NULL, \ + PRIMARY KEY (ver, cap, account_id) \ + FOREIGN KEY('account_id') REFERENCES 'account'('account_id') ON DELETE CASCADE \ + );"]; + [db executeNonQuery:@"DROP TABLE IF EXISTS ver_timestamp;"]; + }]; + + //check if device id changed and invalidate state, if so //but do so only for non-sandbox (e.g. non-development) installs if(![[HelperTools defaultsDB] boolForKey:@"isSandboxAPNS"]) diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index 619ab6dec7..5e7b19c30b 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -617,8 +617,7 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode NSSet* features = [NSSet setWithArray:[iqNode find:@"{http://jabber.org/protocol/disco#info}query/feature@var"]]; NSArray* forms = [iqNode find:@"{http://jabber.org/protocol/disco#info}query/{jabber:x:data}x"]; NSString* ver = [HelperTools getEntityCapsHashForIdentities:identities andFeatures:features andForms:forms]; - [[DataLayer sharedInstance] setCaps:features forVer:ver]; - [account markCapsQueryCompleteFor:ver]; + [[DataLayer sharedInstance] setCaps:features forVer:ver onAccountNo:account.accountNo]; //send out kMonalContactRefresh notification [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:account userInfo:@{ diff --git a/Monal/Classes/xmpp.h b/Monal/Classes/xmpp.h index 158380c27e..3114dc3edf 100644 --- a/Monal/Classes/xmpp.h +++ b/Monal/Classes/xmpp.h @@ -244,8 +244,6 @@ typedef void (^monal_iq_handler_t)(XMPPIQ* _Nullable); -(void) createInvitationWithCompletion:(monal_id_block_t) completion; --(void) markCapsQueryCompleteFor:(NSString*) ver; - @end NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 361eb437c0..c54d7e110d 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -49,7 +49,7 @@ @import AVFoundation; @import WebRTC; -#define STATE_VERSION 10 +#define STATE_VERSION 11 #define CONNECT_TIMEOUT 7.0 #define IQ_TIMEOUT 60.0 NSString* const kQueueID = @"queueID"; @@ -102,7 +102,6 @@ @interface xmpp() NSMutableArray* _smacksAckHandler; NSMutableDictionary* _iqHandlers; NSMutableArray* _reconnectionHandlers; - NSMutableSet* _runningCapsQueries; NSMutableDictionary* _runningMamQueries; BOOL _SRVDiscoveryDone; BOOL _startTLSComplete; @@ -259,7 +258,6 @@ -(void) setupObjects _iqHandlers = [NSMutableDictionary new]; _reconnectionHandlers = [NSMutableArray new]; _mamPageArrays = [NSMutableDictionary new]; - _runningCapsQueries = [NSMutableSet new]; _runningMamQueries = [NSMutableDictionary new]; _inCatchup = [NSMutableDictionary new]; _pipeliningState = kPipelinedNothing; @@ -1943,18 +1941,10 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR if(!ver || ![ver isEqualToString:newVer]) //caps hash of resource changed [[DataLayer sharedInstance] setVer:newVer forUser:presenceNode.fromUser andResource:presenceNode.fromResource onAccountNo:self.accountNo]; - if(![[DataLayer sharedInstance] getCapsforVer:newVer]) + if(![[DataLayer sharedInstance] getCapsforVer:newVer onAccountNo:self.accountNo]) { - if([_runningCapsQueries containsObject:newVer]) - { - DDLogDebug(@"Presence included unknown caps hash %@, but disco query already running", newVer); - shouldQueryCaps = NO; - } - else - { - DDLogInfo(@"Presence included unknown caps hash %@, querying disco", newVer); - shouldQueryCaps = YES; - } + DDLogInfo(@"Presence included unknown caps hash %@, querying disco", newVer); + shouldQueryCaps = YES; } } @@ -1964,7 +1954,6 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR [discoInfo setiqTo:presenceNode.from]; [discoInfo setDiscoInfoNode]; [self sendIq:discoInfo withHandler:$newHandler(MLIQProcessor, handleEntityCapsDisco)]; - [_runningCapsQueries addObject:newVer]; } } @@ -3427,7 +3416,6 @@ -(void) realPersistState [values setObject:[self.pubsub getInternalData] forKey:@"pubsubData"]; [values setObject:[self.mucProcessor getInternalState] forKey:@"mucState"]; - [values setObject:[self->_runningCapsQueries copy] forKey:@"runningCapsQueries"]; [values setObject:[self->_runningMamQueries copy] forKey:@"runningMamQueries"]; [values setObject:[NSNumber numberWithBool:self->_loggedInOnce] forKey:@"loggedInOnce"]; [values setObject:[NSNumber numberWithBool:self.connectionProperties.usingCarbons2] forKey:@"usingCarbons2"]; @@ -3683,9 +3671,6 @@ -(void) realReadState if([dic objectForKey:@"mucState"]) [self.mucProcessor setInternalState:[dic objectForKey:@"mucState"]]; - if([dic objectForKey:@"runningCapsQueries"]) - _runningCapsQueries = [[dic objectForKey:@"runningCapsQueries"] mutableCopy]; - if([dic objectForKey:@"runningMamQueries"]) _runningMamQueries = [[dic objectForKey:@"runningMamQueries"] mutableCopy]; @@ -3967,9 +3952,6 @@ -(void) initSession //clear list of running mam queries _runningMamQueries = [NSMutableDictionary new]; - //clear list of running caps queries - _runningCapsQueries = [NSMutableSet new]; - //clear old catchup state (technically all stanzas still in delayedMessageStanzas could have also been //in the parseQueue in the last run and deleted there) //--> no harm in deleting them when starting a new session (but DON'T DELETE them when resuming the old smacks session) @@ -5306,11 +5288,6 @@ -(void) removeFromServerWithCompletion:(void (^)(NSString* _Nullable error)) com }]; } --(void) markCapsQueryCompleteFor:(NSString*) ver -{ - [_runningCapsQueries removeObject:ver]; -} - -(void) publishRosterName:(NSString* _Nullable) rosterName { DDLogInfo(@"Publishing own nickname: '%@'", rosterName); From 8322a0d0569274289128ae8129acf5dd6cae4e2f Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 22:57:04 +0100 Subject: [PATCH 088/115] Fix xmlquery testcases --- Monal/Classes/MonalAppDelegate.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index d652f3bb5c..51d1a6bf2a 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -73,7 +73,7 @@ -(void) runParserTests \n\ SCRAM-SHA-1PLAIN\n\ http://jabber.org/protocol/muc#roominfo200testchat gruppe\n\ - \n\ + \n\ \n\ \n\ \n\ @@ -124,7 +124,7 @@ -(void) runParserTests DDLogDebug(@"Query: '%@', result: '%@'", query, result); } NSString* specialQuery1 = @"//{http://jabber.org/protocol/pubsub}pubsub/subscription"; - id result = [parsedStanza find:specialQuery1, @"result", @"eu.siacs.conversations.axolotl.devicelist", "subscribed", @"test1@xmpp.eightysoft.de"]; + id result = [parsedStanza find:specialQuery1, @"result", @"eu.siacs.conversations.axolotl.devicelist", "subscribed", @"user@example.com"]; DDLogDebug(@"Query: '%@', result: '%@'", specialQuery1, result); //handle gajim disco hash testcase From 33c7b28319d4cd3a921ac04fb1a3974489ac158d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 28 Feb 2024 23:14:33 +0100 Subject: [PATCH 089/115] Don't show login success while still loading omemo bundles --- Monal/Classes/MLOMEMO.m | 15 ++++++++++++--- Monal/Classes/WelcomeLogIn.swift | 8 ++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 781a55f5b4..77a7b1b460 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -254,6 +254,10 @@ -(void) repairQueuedSessions [self queryOMEMOBundleFrom:jid andDevice:rid]; } + //check bundle fetch status and inform ui if we are now catchupDone *and* all bundles are fetched + //(this method is only called by the catchupDone handler above or by the devicelist fetch triggered by the catchupDone handler) + [self checkBundleFetchCount]; + DDLogVerbose(@"New state: %@", self.state); } @@ -627,9 +631,8 @@ -(void) handleBundleWithInvalidEntryForJid:(NSString*) jid andRid:(NSNumber*) ri } } --(void) decrementBundleFetchCount +-(BOOL) checkBundleFetchCount { - //use catchupDone for better UX on first login if(self.openBundleFetchCnt == 0 && self.state.catchupDone) { //update bundle fetch status (e.g. complete) @@ -638,8 +641,14 @@ -(void) decrementBundleFetchCount [[MLNotificationQueue currentQueue] postNotificationName:kMonalFinishedOmemoBundleFetch object:self userInfo:@{ @"accountNo": self.account.accountNo, }]; + return YES; } - else + return NO; +} + +-(void) decrementBundleFetchCount +{ + if(![self checkBundleFetchCount]) { //update bundle fetch status (e.g. pending) self.openBundleFetchCnt--; diff --git a/Monal/Classes/WelcomeLogIn.swift b/Monal/Classes/WelcomeLogIn.swift index 671c2eb528..fdf2ea9952 100644 --- a/Monal/Classes/WelcomeLogIn.swift +++ b/Monal/Classes/WelcomeLogIn.swift @@ -27,6 +27,7 @@ struct WelcomeLogIn: View { @State private var errorObserverEnabled = false @State private var newAccountNo: NSNumber? = nil @State private var loginComplete = false + @State private var isLoadingOmemoBundles = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) @ObservedObject private var overlay = LoadingOverlayState() @@ -262,6 +263,7 @@ struct WelcomeLogIn: View { .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalUpdateBundleFetchStatus")).receive(on: RunLoop.main)) { notification in if let notificationAccountNo = notification.userInfo?["accountNo"] as? NSNumber, let completed = notification.userInfo?["completed"] as? NSNumber, let all = notification.userInfo?["all"] as? NSNumber, let newAccountNo : NSNumber = self.newAccountNo { if(notificationAccountNo.intValue == newAccountNo.intValue) { + isLoadingOmemoBundles = true DispatchQueue.main.async { showLoadingOverlay( overlay, @@ -272,10 +274,9 @@ struct WelcomeLogIn: View { } } } - /* .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalFinishedOmemoBundleFetch")).receive(on: RunLoop.main)) { notification in if let notificationAccountNo = notification.userInfo?["accountNo"] as? NSNumber, let newAccountNo : NSNumber = self.newAccountNo { - if(notificationAccountNo.intValue == newAccountNo.intValue) { + if(notificationAccountNo.intValue == newAccountNo.intValue && isLoadingOmemoBundles) { DispatchQueue.main.async { self.loginComplete = true showSuccessAlert() @@ -283,10 +284,9 @@ struct WelcomeLogIn: View { } } } - */ .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalFinishedCatchup")).receive(on: RunLoop.main)) { notification in if let xmppAccount = notification.object as? xmpp, let newAccountNo : NSNumber = self.newAccountNo { - if(xmppAccount.accountNo.intValue == newAccountNo.intValue) { + if(xmppAccount.accountNo.intValue == newAccountNo.intValue && !isLoadingOmemoBundles) { DispatchQueue.main.async { self.loginComplete = true showSuccessAlert() From 1c2bab203b1e2aa91b70d744ada7dfa7ae82d53b Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 29 Feb 2024 00:07:02 +0100 Subject: [PATCH 090/115] Don't process ancient muc invites while backscrolling --- Monal/Classes/MLMessageProcessor.m | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index 2475532b54..a783006f83 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -208,7 +208,7 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag //ignore muc PMs (after discussion with holger we don't want to support that) if( - ![[messageNode findFirst:@"/@type"] isEqualToString:@"groupchat"] && [messageNode check:@"{http://jabber.org/protocol/muc#user}x"] && + ![messageNode check:@"/"] && [messageNode check:@"{http://jabber.org/protocol/muc#user}x"] && ![messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] && [messageNode check:@"body#"] ) { @@ -293,7 +293,14 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag } //handle muc status changes or invites (this checks for the muc namespace itself) - if([account.mucProcessor processMessage:messageNode]) + if(isMLhistory) + { + if([messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"] || ([messageNode check:@"{jabber:x:conference}x@jid"] && [[messageNode findFirst:@"{jabber:x:conference}x@jid"] length] > 0)) + return nil; //stop processing because this is a (mediated) muc invite received through backscrolling history + else + ; //continue processing for backscrolling history but don't call mucProcessor.processMessage to not process ancient status/memberlist updates + } + else if([account.mucProcessor processMessage:messageNode]) { DDLogVerbose(@"Muc processor said we have to stop message processing here..."); return nil; //the muc processor said we have stop processing From bac6270a43d76610cce034bd63693f29238fb622 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 29 Feb 2024 00:31:08 +0100 Subject: [PATCH 091/115] Fix rich alert displaying in dark mode (last time was incomplete) --- Monal/Classes/AVCallUI.swift | 4 ++-- Monal/Classes/ContactResources.swift | 4 ++-- Monal/Classes/RichAlert.swift | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Monal/Classes/AVCallUI.swift b/Monal/Classes/AVCallUI.swift index 9cc9c7a191..bd1ad177f6 100644 --- a/Monal/Classes/AVCallUI.swift +++ b/Monal/Classes/AVCallUI.swift @@ -572,7 +572,7 @@ struct AVCallUI: View { dismissButton: .default(Text("OK")) ) } - .richAlert(isPresented:$showSecurityHelpAlert, title:Text("Call security help").foregroundColor(.black)) { + .richAlert(isPresented:$showSecurityHelpAlert, title:Text("Call security help")) { VStack(alignment: .leading) { HStack { Image(systemName: "xmark.shield.fill") @@ -606,7 +606,7 @@ struct AVCallUI: View { }.font(Font.body.weight(showSecurityHelpAlert == .trusted ? .heavy : .medium)) Text("This means your call is encrypted and the remote party was verified using OMEMO encryption.\nYou manually verified the used OMEMO keys and no Man-In-The-Middle can take place.") Spacer().frame(height: 20) - }.foregroundColor(.black) + } } .onAppear { //force portrait mode and lock ui there diff --git a/Monal/Classes/ContactResources.swift b/Monal/Classes/ContactResources.swift index 726869ab01..cc691e7c8f 100644 --- a/Monal/Classes/ContactResources.swift +++ b/Monal/Classes/ContactResources.swift @@ -67,7 +67,7 @@ struct ContactResources: View { } } } - .richAlert(isPresented:$showCaps, title:Text("XMPP Capabilities").foregroundColor(.black)) { resource in + .richAlert(isPresented:$showCaps, title:Text("XMPP Capabilities")) { resource in VStack(alignment: .leading) { Text("The resource '\(resource)' has the following capabilities:") .font(Font.body.weight(.semibold)) @@ -90,7 +90,7 @@ struct ContactResources: View { } } } - }.foregroundColor(.black) + } } .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("kMonalXmppUserSoftWareVersionRefresh")).receive(on: RunLoop.main)) { notification in if let xmppAccount = notification.object as? xmpp, let softwareInfo = notification.userInfo?["versionInfo"] as? MLContactSoftwareVersionInfo { diff --git a/Monal/Classes/RichAlert.swift b/Monal/Classes/RichAlert.swift index fbe758efa9..4b2a12a329 100644 --- a/Monal/Classes/RichAlert.swift +++ b/Monal/Classes/RichAlert.swift @@ -65,6 +65,7 @@ struct RichAlertView: ViewModifier } .frame(maxHeight: scrollViewContentSize.height) } + .foregroundColor(.primary) .padding([.top, .bottom], 13) .frame(width: 320) .background(Color.background) From b376ca0158cd926ee9f5804b0d527497a41c6620 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 29 Feb 2024 00:30:54 +0100 Subject: [PATCH 092/115] Fix some more omemo own devicelist handling bugs --- Monal/Classes/MLOMEMO.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 77a7b1b460..d64005b3cf 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -402,8 +402,10 @@ -(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe } - //retrigger queued key transport elements for this jid (if any) - [self retriggerKeyTransportElementsForJid:jid]; + if([self.account.connectionProperties.identity.jid isEqualToString:jid]) + [self repairQueuedSessions]; //now try to repair all broken sessions (our catchup is now really done) + else + [self retriggerKeyTransportElementsForJid:jid]; //retrigger queued key transport elements for this jid (if any) $$ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString*) source @@ -648,11 +650,13 @@ -(BOOL) checkBundleFetchCount -(void) decrementBundleFetchCount { + //update bundle fetch status (e.g. pending) + self.openBundleFetchCnt--; + self.closedBundleFetchCnt++; + + //check if we should send a bundle fetch status update or if checkBundleFetchCount already sent the final finished notification for us if(![self checkBundleFetchCount]) { - //update bundle fetch status (e.g. pending) - self.openBundleFetchCnt--; - self.closedBundleFetchCnt++; [[MLNotificationQueue currentQueue] postNotificationName:kMonalUpdateBundleFetchStatus object:self userInfo:@{ @"accountNo": self.account.accountNo, @"completed": @(self.closedBundleFetchCnt), From 3875ef4ebd8549e0701e36b06015c323f0a8965d Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 29 Feb 2024 01:19:21 +0100 Subject: [PATCH 093/115] Add logtag to irq timeout logging --- Monal/Classes/xmpp.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index c54d7e110d..00276c6582 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -5045,7 +5045,7 @@ -(void) updateIqHandlerTimeouts _iqHandlers[iqid][@"timeout"] = @([_iqHandlers[iqid][@"timeout"] doubleValue] - 1.0); if([_iqHandlers[iqid][@"timeout"] doubleValue] < 0.0) { - DDLogWarn(@"Timeout of handler triggered: %@", _iqHandlers[iqid]); + DDLogWarn(@"%@: Timeout of handler triggered: %@", _logtag, _iqHandlers[iqid]); //only force save state after calling a handler //(timeout changes that don't make it to disk only extend the timeout by a few seconds but don't have any negative sideeffect) stateUpdated = YES; @@ -5084,7 +5084,7 @@ -(void) updateIqHandlerTimeouts }]] waitUntilFinished:NO]; } else - DDLogWarn(@"iq handler for '%@' vanished while switching to receive queue", iqid); + DDLogError(@"%@: iq handler for '%@' vanished while switching to receive queue", _logtag, iqid); } } //now delete iqs marked for deletion From b92645017b27506f8e3efce8095b20e8ac73c64f Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:21:40 +0100 Subject: [PATCH 094/115] Only show group / channel modification button if moderator --- Monal/Classes/ContactDetails.swift | 38 ++++++++++++++++-------- Monal/Classes/ContactDetailsHeader.swift | 2 +- Monal/Classes/DataLayer.h | 1 + Monal/Classes/DataLayer.m | 10 +++++++ Monal/Classes/GroupDetailsEdit.swift | 13 ++++---- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index c721bb9709..12b75b824b 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -12,16 +12,28 @@ import monalxmpp struct ContactDetails: View { var delegate: SheetDismisserProtocol - @StateObject var contact: ObservableKVOWrapper - @State private var showingBlockContactConfirmation = false - @State private var showingCannotBlockAlert = false - @State private var showingRemoveContactConfirmation = false - @State private var showingAddContactConfirmation = false - @State private var showingClearHistoryConfirmation = false - @State private var showingResetOmemoSessionConfirmation = false - @State private var showingCannotEncryptAlert = false - @State private var showingShouldDisableEncryptionAlert = false - @State private var isEditingNickname = false + @ObservedObject var contact: ObservableKVOWrapper + @State private var showingBlockContactConfirmation: Bool = false + @State private var showingCannotBlockAlert: Bool = false + @State private var showingRemoveContactConfirmation: Bool = false + @State private var showingAddContactConfirmation: Bool = false + @State private var showingClearHistoryConfirmation: Bool = false + @State private var showingResetOmemoSessionConfirmation: Bool = false + @State private var showingCannotEncryptAlert: Bool = false + @State private var showingShouldDisableEncryptionAlert: Bool = false + @State private var isEditingNickname: Bool = false + private let isGroupModerator: Bool + + init(delegate: SheetDismisserProtocol, contact: ObservableKVOWrapper) { + self.delegate = delegate + self.contact = contact + if contact.isGroup { + let ownRole = DataLayer.sharedInstance().getOwnRole(inGroupOrChannel: contact.obj) + self.isGroupModerator = (ownRole == "moderator") + } else { + self.isGroupModerator = false + } + } var body: some View { Form { @@ -348,13 +360,13 @@ struct ContactDetails: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .navigationBarTitle(contact.contactDisplayName as String, displayMode: .inline) - .navigationBarGroupEditButton(contact: contact) + .navigationBarGroupEditButton(contact: contact, isGroupModerator: self.isGroupModerator) } } extension View { - func navigationBarGroupEditButton(contact: ObservableKVOWrapper) -> some View { - if contact.isGroup { + func navigationBarGroupEditButton(contact: ObservableKVOWrapper, isGroupModerator: Bool) -> some View { + if contact.isGroup && isGroupModerator { return AnyView(self.toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink(destination: LazyClosureView(GroupDetailsEdit(contact: contact))) { diff --git a/Monal/Classes/ContactDetailsHeader.swift b/Monal/Classes/ContactDetailsHeader.swift index e8676bd51d..e99e4227f6 100644 --- a/Monal/Classes/ContactDetailsHeader.swift +++ b/Monal/Classes/ContactDetailsHeader.swift @@ -13,7 +13,7 @@ import monalxmpp struct ContactDetailsHeader: View { var delegate: SheetDismisserProtocol - @StateObject var contact: ObservableKVOWrapper + @ObservedObject var contact: ObservableKVOWrapper @State private var navigationAction: String? var body: some View { diff --git a/Monal/Classes/DataLayer.h b/Monal/Classes/DataLayer.h index 0d7dc3cf89..09d3d41faf 100644 --- a/Monal/Classes/DataLayer.h +++ b/Monal/Classes/DataLayer.h @@ -115,6 +115,7 @@ extern NSString* const kMessageTypeFiletransfer; -(void) removeParticipant:(NSDictionary*) participant fromMuc:(NSString*) room forAccountId:(NSNumber*) accountNo; -(NSDictionary* _Nullable) getParticipantForNick:(NSString*) nick inRoom:(NSString*) room forAccountId:(NSNumber*) accountNo; -(NSArray*>*) getMembersAndParticipantsOfMuc:(NSString*) room forAccountId:(NSNumber*) accountNo; +-(NSString*) getOwnRoleInGroupOrChannel:(MLContact*) contact; -(void) addMucFavorite:(NSString*) room forAccountId:(NSNumber*) accountNo andMucNick:(NSString* _Nullable) mucNick; -(NSString*) lastStanzaIdForMuc:(NSString* _Nonnull) room andAccount:(NSNumber* _Nonnull) accountNo; -(void) setLastStanzaId:(NSString*) lastStanzaId forMuc:(NSString* _Nonnull) room andAccount:(NSNumber* _Nonnull) accountNo; diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index 24ca7c97fa..d955f9b1f9 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -1011,6 +1011,16 @@ -(NSDictionary* _Nullable) getParticipantForNick:(NSString*) nick inRoom:(NSStri }]; } +-(NSString*) getOwnRoleInGroupOrChannel:(MLContact*) contact +{ + if(contact == nil) + return nil; + MLAssert(contact.isGroup, @"Function should only be called on a group contact"); + return [self.db idReadTransaction:^{ + return [self.db executeScalar:@"SELECT M.role FROM muc_participants AS M INNER JOIN account AS A ON M.account_id=A.account_id WHERE M.room=? AND A.account_id=? AND (A.username || '@' || A.domain) == M.participant_jid" andArguments:@[contact.contactJid, contact.accountId]]; + }]; +} + -(void) addMucFavorite:(NSString*) room forAccountId:(NSNumber*) accountNo andMucNick:(NSString* _Nullable) mucNick { [self.db voidWriteTransaction:^{ diff --git a/Monal/Classes/GroupDetailsEdit.swift b/Monal/Classes/GroupDetailsEdit.swift index 44a4e4e539..48f00c7873 100644 --- a/Monal/Classes/GroupDetailsEdit.swift +++ b/Monal/Classes/GroupDetailsEdit.swift @@ -13,7 +13,8 @@ struct GroupDetailsEdit: View { @ObservedObject var contact: ObservableKVOWrapper private let account: xmpp? - @State private var showingSheet = false + @State private var showingSheetEditName = false + @State private var showingSheetEditSubject = false @State private var inputImage: UIImage? @State private var showingImagePicker = false @@ -48,7 +49,7 @@ struct GroupDetailsEdit: View { Section { if #available(iOS 15.0, *) { Button(action: { - showingSheet.toggle() + showingSheetEditName.toggle() }) { HStack { Image(systemName: "person.2") @@ -56,11 +57,11 @@ struct GroupDetailsEdit: View { Spacer() } } - .sheet(isPresented: $showingSheet) { + .sheet(isPresented: $showingSheetEditName) { LazyClosureView(EditGroupName(contact: contact)) } Button(action: { - showingSheet.toggle() + showingSheetEditSubject.toggle() }) { HStack { Image(systemName: "pencil") @@ -68,13 +69,13 @@ struct GroupDetailsEdit: View { Spacer() } } - .sheet(isPresented: $showingSheet) { + .sheet(isPresented: $showingSheetEditSubject) { LazyClosureView(EditGroupSubject(contact: contact)) } } } } - .navigationTitle("Edit group") + .navigationTitle((contact.obj.mucType == "group") ? "Edit group" : "Edit channel") .onChange(of:inputImage) { _ in self.account!.mucProcessor.publishAvatar(inputImage, forMuc: contact.contactJid) } From 2aa8b2f33fe181c9b236fe95ed3b1ae414c6a2bf Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Fri, 1 Mar 2024 01:52:45 +0100 Subject: [PATCH 095/115] Do not silently switch call type when migrating call --- Monal/Classes/MLCall.m | 1 + Monal/Classes/MLVoIPProcessor.m | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/MLCall.m b/Monal/Classes/MLCall.m index f0d115406d..03705f57ea 100644 --- a/Monal/Classes/MLCall.m +++ b/Monal/Classes/MLCall.m @@ -496,6 +496,7 @@ -(void) migrateTo:(MLCall*) otherCall DDLogDebug(@"%@: Preparing this call for new webrtc connection...", [self short]); self.jmiid = otherCall.jmiid; self.fullRemoteJid = otherCall.fullRemoteJid; + self.callType = otherCall.callType; self.isConnected = NO; self.isReconnecting = YES; self.finishReason = MLCallFinishReasonUnknown; diff --git a/Monal/Classes/MLVoIPProcessor.m b/Monal/Classes/MLVoIPProcessor.m index bcdf8e7a63..a3bcb37fb8 100644 --- a/Monal/Classes/MLVoIPProcessor.m +++ b/Monal/Classes/MLVoIPProcessor.m @@ -264,13 +264,21 @@ -(void) handleIncomingVoipCall:(NSNotification*) notification //handle tie breaking: one party migrates the call to other device else if(existingCall.state < MLCallStateFinished) //call already running { - DDLogInfo(@"Migrating from new call to existing call: %@", existingCall); - [existingCall migrateTo:newCall]; - - //drop new call after migration to make sure it does not interfere with our existing call - DDLogInfo(@"Dropping newCall '%@' in favor of migrated existingCall '%@' ...", [newCall short], [existingCall short]); - newCall = nil; - + if(newCall.callType == existingCall.callType) + { + DDLogInfo(@"Migrating from new call to existing call: %@", existingCall); + [existingCall migrateTo:newCall]; + + //drop new call after migration to make sure it does not interfere with our existing call + DDLogInfo(@"Dropping newCall '%@' in favor of migrated existingCall '%@' ...", [newCall short], [existingCall short]); + newCall = nil; + } + else + { + existingCall.tieBreak = YES; //will be ignored if call was connected, but it doesn't hurt either + [existingCall end]; + [self processIncomingCall:notification.userInfo withCompletion:nil]; + } return; } unreachable(); From ecb7035d715778e33587d97fae7fd8f453c4c966 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:47:50 +0100 Subject: [PATCH 096/115] Improve group edit UI --- Monal/Classes/EditGroupName.swift | 13 +++++-------- Monal/Classes/EditGroupSubject.swift | 6 +++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Monal/Classes/EditGroupName.swift b/Monal/Classes/EditGroupName.swift index c58a29f2cc..5d106d9450 100644 --- a/Monal/Classes/EditGroupName.swift +++ b/Monal/Classes/EditGroupName.swift @@ -24,29 +24,26 @@ struct EditGroupName: View { _groupName = State(wrappedValue: contact.obj.contactDisplayName) self.contact = contact self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp - } var body: some View { + NavigationView { - VStack { Form { - Section { + Section(header: Text("Group name")) { TextField(NSLocalizedString("Group Name (optional)", comment: "placeholder when editing a group name"), text: $groupName, onEditingChanged: { isEditingGroupName = $0 }) .autocorrectionDisabled() .autocapitalization(.none) .addClearButton(isEditing: isEditingGroupName, text:$groupName) } } - } - .navigationTitle("Groupname") - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { + .toolbar { + ToolbarItem(placement: .cancellationAction) { Button("Abort") { dismiss() } } - ToolbarItem(placement: .navigationBarTrailing) { + ToolbarItem(placement: .confirmationAction) { Button("Done") { self.account!.mucProcessor.changeName(ofMuc: contact.contactJid, to: self.groupName) dismiss() diff --git a/Monal/Classes/EditGroupSubject.swift b/Monal/Classes/EditGroupSubject.swift index 821b2fb367..092bc3fd2e 100644 --- a/Monal/Classes/EditGroupSubject.swift +++ b/Monal/Classes/EditGroupSubject.swift @@ -40,7 +40,7 @@ struct EditGroupSubject: View { NavigationView { VStack { Form { - Section { + Section(header: Text("Group Description")) { TextField(NSLocalizedString("Group Description (optional)", comment: "placeholder when editing a group description"), text: $subject, onEditingChanged: { isEditingSubject = $0 }) .multilineTextAlignment(.leading) .versionConditionalLineLimit(10...50) @@ -50,12 +50,12 @@ struct EditGroupSubject: View { } .navigationTitle("Group description") .toolbar { - ToolbarItem(placement: .navigationBarLeading) { + ToolbarItem(placement: .cancellationAction) { Button("Abort") { dismiss() } } - ToolbarItem(placement: .navigationBarTrailing) { + ToolbarItem(placement: .confirmationAction) { Button("Done") { self.account!.mucProcessor.changeSubject(ofMuc: contact.contactJid, to: self.subject) dismiss() From f9790bd32e563be80863ab4b909160bdf9c736c6 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:48:05 +0100 Subject: [PATCH 097/115] do not measure transaction time in simulator --- Monal/Classes/MLSQLite.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MLSQLite.m b/Monal/Classes/MLSQLite.m index 1ae83ef847..f17f812e9b 100644 --- a/Monal/Classes/MLSQLite.m +++ b/Monal/Classes/MLSQLite.m @@ -381,12 +381,16 @@ -(BOOL) boolWriteTransaction:(monal_sqlite_bool_operations_t) operations -(id) idWriteTransaction:(monal_sqlite_operations_t) operations { [self beginWriteTransaction]; +#if !TARGET_OS_SIMULATOR NSDate* startTime = [NSDate date]; +#endif id retval = operations(); - NSDate* endTime = [NSDate date]; [self endWriteTransaction]; +#if !TARGET_OS_SIMULATOR + NSDate* endTime = [NSDate date]; if([endTime timeIntervalSinceDate:startTime] > 0.5) showErrorOnAlpha(nil, @"Write transaction took %fs (longer than 0.5s): %@", (double)[endTime timeIntervalSinceDate:startTime], [NSThread callStackSymbols]); +#endif return retval; } From 4f63b59cabcdc66784a12381ad22e142dffbda85 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 1 Mar 2024 15:17:13 +0100 Subject: [PATCH 098/115] only show edit button in contact details when connected --- Monal/Classes/ContactDetails.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index 12b75b824b..b1aca37275 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -13,6 +13,7 @@ import monalxmpp struct ContactDetails: View { var delegate: SheetDismisserProtocol @ObservedObject var contact: ObservableKVOWrapper + private var account: xmpp? @State private var showingBlockContactConfirmation: Bool = false @State private var showingCannotBlockAlert: Bool = false @State private var showingRemoveContactConfirmation: Bool = false @@ -27,6 +28,8 @@ struct ContactDetails: View { init(delegate: SheetDismisserProtocol, contact: ObservableKVOWrapper) { self.delegate = delegate self.contact = contact + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId) as xmpp? + if contact.isGroup { let ownRole = DataLayer.sharedInstance().getOwnRole(inGroupOrChannel: contact.obj) self.isGroupModerator = (ownRole == "moderator") @@ -360,13 +363,13 @@ struct ContactDetails: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .navigationBarTitle(contact.contactDisplayName as String, displayMode: .inline) - .navigationBarGroupEditButton(contact: contact, isGroupModerator: self.isGroupModerator) + .navigationBarGroupEditButton(contact: contact, isGroupModerator: self.isGroupModerator, account: self.account) } } extension View { - func navigationBarGroupEditButton(contact: ObservableKVOWrapper, isGroupModerator: Bool) -> some View { - if contact.isGroup && isGroupModerator { + func navigationBarGroupEditButton(contact: ObservableKVOWrapper, isGroupModerator: Bool, account: xmpp?) -> some View { + if contact.isGroup && isGroupModerator && (account != nil && account!.accountState == xmppState.stateBound) { return AnyView(self.toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink(destination: LazyClosureView(GroupDetailsEdit(contact: contact))) { From 034f6c5bdfcb1f23b8afb3b80c4ec005149ac4d9 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 1 Mar 2024 20:51:51 +0100 Subject: [PATCH 099/115] group chat: permissions UI --- Monal/Classes/MemberList.swift | 152 ++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 23 deletions(-) diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 390d966dc9..cfbb5eed75 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -23,6 +23,8 @@ struct MemberList: View { @State private var showAlert = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) + @State private var selectedMember: MLContact? + init(mucContact: ObservableKVOWrapper?) { self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact!.accountId)! as xmpp self.group = mucContact!; @@ -65,35 +67,34 @@ struct MemberList: View { } var body: some View { - // This is the invisible NavigationLink hack again... - NavigationLink(destination:LazyClosureView(ContactPicker(account: self.account!, selectedContacts: $contactsToAdd)), isActive: $openAccountSelection){}.hidden().disabled(true) // navigation happens as soon as our button sets navigateToQRCodeView to true... List { Section(header: Text(self.group.obj.contactDisplayName)) { ForEach(self.memberList, id: \.self.obj) { contact in - NavigationLink(destination: LazyClosureView(ContactDetails(delegate: SheetDismisserProtocol(), contact: contact)), label: { - ZStack(alignment: .topLeading) { - HStack(alignment: .center) { - Image(uiImage: contact.avatar) - .resizable() - .frame(width: 40, height: 40, alignment: .center) - Text(contact.contactDisplayName as String) - Spacer() - if let contactAffiliation = self.affiliation[contact.contactJid] { - if contactAffiliation == "owner" { - Text(NSLocalizedString("Owner", comment: "")) - } else if contactAffiliation == "admin" { - Text(NSLocalizedString("Admin", comment: "")) - } else if contactAffiliation == "member" { - Text(NSLocalizedString("Member", comment: "")) - } else if contactAffiliation == "outcast" { - Text(NSLocalizedString("Outcast", comment: "")) - } else { - Text(NSLocalizedString("", comment: "")) - } - } + HStack(alignment: .center) { + Image(uiImage: contact.avatar) + .resizable() + .frame(width: 40, height: 40, alignment: .center) + Text(contact.contactDisplayName as String) + Spacer() + if let contactAffiliation = self.affiliation[contact.contactJid] { + if contactAffiliation == "owner" { + Text(NSLocalizedString("Owner", comment: "")) + } else if contactAffiliation == "admin" { + Text(NSLocalizedString("Admin", comment: "")) + } else if contactAffiliation == "member" { + Text(NSLocalizedString("Member", comment: "")) + } else if contactAffiliation == "outcast" { + Text(NSLocalizedString("Outcast", comment: "")) + } else { + Text(NSLocalizedString("", comment: "")) } } + } + .onTapGesture(perform: { + if(contact.obj.contactJid != self.account?.connectionProperties.identity.jid) { + self.selectedMember = contact.obj + } }) .deleteDisabled( !ownUserHasPermissionToRemove(contact: contact) @@ -109,9 +110,114 @@ struct MemberList: View { }.alert(isPresented: $showAlert, content: { Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton: .default(alertPrompt.dismissLabel)) }) + .sheet(item: self.$selectedMember, content: { selectedMemberUnobserved in + let selectedMember = ObservableKVOWrapper(selectedMemberUnobserved) + VStack { + Form { + Section { + HStack { + Spacer() + Image(uiImage: selectedMember.avatar) + .resizable() + .frame(width: 150, height: 150, alignment: .center) + Spacer() + } + HStack { + Spacer() + Text(selectedMember.contactDisplayName as String) + Spacer() + } + } + Section(header: Text("Configure Membership")) { + if self.ownAffiliation == "owner" && self.affiliation[selectedMember.contactJid] == "owner" { + makeAdmin(selectedMember) + makeMember(selectedMember) + removeUserButton(selectedMember) + block(selectedMember) + } + if self.ownAffiliation == "owner" && self.affiliation[selectedMember.contactJid] == "admin" { + makeOwner(selectedMember) + makeMember(selectedMember) + removeUserButton(selectedMember) + block(selectedMember) + } + if self.ownAffiliation == "owner" && self.affiliation[selectedMember.contactJid] == "member" { + makeOwner(selectedMember) + makeAdmin(selectedMember) + removeUserButton(selectedMember) + block(selectedMember) + } + if self.ownAffiliation == "admin" && self.affiliation[selectedMember.contactJid] == "member" { + removeUserButton(selectedMember) + block(selectedMember) + } + if (self.ownAffiliation == "admin" || self.ownAffiliation == "owner") && self.affiliation[selectedMember.contactJid] == "outcast" { + makeMember(selectedMember) + } + } + } + } + }) } .navigationBarTitle("Group Members", displayMode: .inline) } + + func removeUserButton(_ selectedMember: ObservableKVOWrapper) -> some View { + if #available(iOS 15, *) { + return Button(role: .destructive, action: { + self.account!.mucProcessor.setAffiliation("none", ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.setAndShowAlert(title: "Member deleted", description: selectedMember.contactJid) + if let index = self.memberList.firstIndex(of: selectedMember) { + self.memberList.remove(at: index) + } + self.selectedMember = nil + }) { + Text("Remove from group") + } + } else { + return AnyView(EmptyView()) + } + } + + func permissionsButton(_ selectedMember: ObservableKVOWrapper, permission: String, @ViewBuilder label: () -> Label) -> some View { + return Button(action: { + self.account!.mucProcessor.setAffiliation(permission, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.affiliation[selectedMember.contactJid] = permission + }) { + label() + } + } + + func makeOwner(_ selectedMember: ObservableKVOWrapper) -> some View { + return permissionsButton(selectedMember, permission: "owner", label: { + Text("Make owner") + }) + } + + func makeAdmin(_ selectedMember: ObservableKVOWrapper) -> some View { + return permissionsButton(selectedMember, permission: "admin", label: { + Text("Make admin") + }) + } + + func makeMember(_ selectedMember: ObservableKVOWrapper) -> some View { + return permissionsButton(selectedMember, permission: "member", label: { + Text("Make member") + }) + } + + func block(_ selectedMember: ObservableKVOWrapper) -> AnyView { + if self.group.mucType != "group" { + return AnyView(Button(action: { + self.account!.mucProcessor.setAffiliation("outcast", ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.affiliation[selectedMember.contactJid] = "outcast" + }) { + Text("Block from group") + }) + } else { + return AnyView(EmptyView()) + } + } } struct MemberList_Previews: PreviewProvider { From 2f03fa3b09571312acafa14f38618918697c4fbf Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 1 Mar 2024 20:54:57 +0100 Subject: [PATCH 100/115] less code --- Monal/Classes/MemberList.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index cfbb5eed75..5ed84c77d7 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -208,12 +208,11 @@ struct MemberList: View { func block(_ selectedMember: ObservableKVOWrapper) -> AnyView { if self.group.mucType != "group" { - return AnyView(Button(action: { - self.account!.mucProcessor.setAffiliation("outcast", ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) - self.affiliation[selectedMember.contactJid] = "outcast" - }) { - Text("Block from group") - }) + return AnyView( + permissionsButton(selectedMember, permission: "outcast", label: { + Text("Block grom group") + }) + ) } else { return AnyView(EmptyView()) } From f6536b07260642621ebcb3529a0c3653f878b111 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:03:11 +0100 Subject: [PATCH 101/115] Show spinner while uploading group avatar --- Monal/Classes/GroupDetailsEdit.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Monal/Classes/GroupDetailsEdit.swift b/Monal/Classes/GroupDetailsEdit.swift index 48f00c7873..a7c2e648f3 100644 --- a/Monal/Classes/GroupDetailsEdit.swift +++ b/Monal/Classes/GroupDetailsEdit.swift @@ -17,6 +17,7 @@ struct GroupDetailsEdit: View { @State private var showingSheetEditSubject = false @State private var inputImage: UIImage? @State private var showingImagePicker = false + @ObservedObject private var overlay = LoadingOverlayState() init(contact: ObservableKVOWrapper) { MLAssert(contact.isGroup) @@ -75,10 +76,15 @@ struct GroupDetailsEdit: View { } } } + .addLoadingOverlay(overlay) .navigationTitle((contact.obj.mucType == "group") ? "Edit group" : "Edit channel") .onChange(of:inputImage) { _ in + showLoadingOverlay(overlay, headline: NSLocalizedString("Uploading image...", comment: "")) self.account!.mucProcessor.publishAvatar(inputImage, forMuc: contact.contactJid) } + .onChange(of:contact.avatar as UIImage) { _ in + hideLoadingOverlay(overlay) + } } } From 1015a55838e166098b8feb54441b80a3389bbba3 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 2 Mar 2024 08:57:13 +0100 Subject: [PATCH 102/115] group UI: adding group members to existing group --- Monal/Classes/ContactPicker.swift | 62 +++++++++++++++++++----------- Monal/Classes/MemberList.swift | 30 ++++++++++++--- Monal/Classes/OmemoKeys.swift | 2 +- Monal/Classes/SwiftHelpers.swift | 9 +++-- Monal/Classes/SwiftuiHelpers.swift | 5 ++- 5 files changed, 74 insertions(+), 34 deletions(-) diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index b2fec202f4..4bd989f617 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -13,11 +13,15 @@ import OrderedCollections struct ContactPickerEntry: View { let contact : ObservableKVOWrapper let isPicked: Bool + let isExistingMember: Bool var body:some View { ZStack(alignment: .topLeading) { HStack(alignment: .center) { - if(isPicked) { + if(isExistingMember) { + Image(systemName: "checkmark.circle") + .foregroundColor(.gray) + } else if(isPicked) { Image(systemName: "checkmark.circle") .foregroundColor(.blue) } else { @@ -38,31 +42,34 @@ struct ContactPickerEntry: View { struct ContactPicker: View { @Environment(\.presentationMode) private var presentationMode - @State var contacts: [ObservableKVOWrapper] + @State var contacts: OrderedSet> let account: xmpp - @Binding var selectedContacts : OrderedSet> // already selected when going into the view - @State var searchFieldInput = "" + @Binding var selectedContacts: OrderedSet> + let existingMembers: OrderedSet> + @State var searchText = "" @State var isEditingSearchInput: Bool = false init(account: xmpp, selectedContacts: Binding>>) { + self.init(account: account, selectedContacts: selectedContacts, existingMembers: OrderedSet()) + } + + init(account: xmpp, selectedContacts: Binding>>, existingMembers: OrderedSet>) { self.account = account self._selectedContacts = selectedContacts + self.existingMembers = existingMembers - var contactsTmp: [ObservableKVOWrapper] = Array() + var contactsTmp: OrderedSet> = OrderedSet() for contact in DataLayer.sharedInstance().possibleGroupMembers(forAccount: account.accountNo) { contactsTmp.append(ObservableKVOWrapper(contact)) } _contacts = State(wrappedValue: contactsTmp) } - func matchesSearch(contact: ObservableKVOWrapper) -> Bool { - // TODO better lookup - if searchFieldInput.isEmpty == true { - return true - } else { - return (contact.contactDisplayName as String).lowercased().contains(searchFieldInput.lowercased()) || - (contact.contactJid as String).contains(searchFieldInput.lowercased()) + private var searchResults : OrderedSet> { + searchText.isEmpty ? self.contacts : self.contacts.filter { + ($0.contactDisplayName as String).lowercased().contains(searchText.lowercased()) || + ($0.contactJid as String).contains(searchText.lowercased()) } } @@ -72,24 +79,23 @@ struct ContactPicker: View { .navigationTitle("Contact Lists") } else { List { - Section { - TextField(NSLocalizedString("Search contacts", comment: "placeholder in contact picker"), text: $searchFieldInput, onEditingChanged: { isEditingSearchInput = $0 }) - .addClearButton(isEditing: isEditingSearchInput, text:$searchFieldInput) - } - ForEach(Array(contacts.enumerated()), id: \.element.obj.contactJid) { idx, contact in - if matchesSearch(contact: contact) { - let contactIsSelected = self.selectedContacts.contains(contact); - ContactPickerEntry(contact: contact, isPicked: contactIsSelected) - .onTapGesture(perform: { + ForEach(searchResults, id: \.self.obj) { contact in + let contactIsSelected = self.selectedContacts.contains(contact); + let contactIsAlreadyMember = self.existingMembers.contains(contact); + ContactPickerEntry(contact: contact, isPicked: contactIsSelected, isExistingMember: contactIsAlreadyMember) + .onTapGesture(perform: { + // only allow changes to members that are not already part of the group + if(!contactIsAlreadyMember) { if(contactIsSelected) { self.selectedContacts.remove(contact) } else { self.selectedContacts.append(contact) } - }) - } + } + }) } } + .searchableWithiOSCheck(text: $searchText) .listStyle(.inset) .navigationBarTitle("Contact Selection", displayMode: .inline) .navigationBarBackButtonHidden(true) @@ -103,3 +109,13 @@ struct ContactPicker: View { } } } + +extension View { + func searchableWithiOSCheck(text: Binding, prompt: Text? = nil) -> some View { + if #available(iOS 15.0, *) { + return self.searchable(text: text, placement: .automatic, prompt: prompt) + } else { + return self + } + } +} diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 5ed84c77d7..9077b09107 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -11,14 +11,13 @@ import monalxmpp import OrderedCollections struct MemberList: View { - @State private var memberList: [ObservableKVOWrapper] + @State private var memberList: OrderedSet> @State private var affiliation: Dictionary @ObservedObject var group: ObservableKVOWrapper private let account: xmpp? private var ownAffiliation: String = "none"; @State private var openAccountSelection : Bool = false - @State private var contactsToAdd : OrderedSet> = [] @State private var showAlert = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) @@ -69,6 +68,11 @@ struct MemberList: View { var body: some View { List { Section(header: Text(self.group.obj.contactDisplayName)) { + if self.ownAffiliation == "owner" || self.ownAffiliation == "admin" { + NavigationLink(destination: LazyClosureView(ContactPicker(account: self.account!, selectedContacts: $memberList, existingMembers: self.memberList)), label: { + Text("Add Group Members") + }) + } ForEach(self.memberList, id: \.self.obj) { contact in HStack(alignment: .center) { @@ -107,7 +111,17 @@ struct MemberList: View { self.setAndShowAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) self.memberList.remove(at: memberIdx.first!) }) - }.alert(isPresented: $showAlert, content: { + } + .onChange(of: self.memberList) { [previousMemberList = self.memberList] newMemberList in + // only handle new members (added via the contact picker + for member in newMemberList { + if !previousMemberList.contains(member) { + // add selected group member with role member + permissionChangeAction(member, permission: "member") + } + } + } + .alert(isPresented: $showAlert, content: { Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton: .default(alertPrompt.dismissLabel)) }) .sheet(item: self.$selectedMember, content: { selectedMemberUnobserved in @@ -179,10 +193,16 @@ struct MemberList: View { } } + func permissionChangeAction(_ selectedMember: ObservableKVOWrapper, permission: String) { + self.account!.mucProcessor.setAffiliation(permission, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.affiliation[selectedMember.contactJid] = permission + } + func permissionsButton(_ selectedMember: ObservableKVOWrapper, permission: String, @ViewBuilder label: () -> Label) -> some View { return Button(action: { - self.account!.mucProcessor.setAffiliation(permission, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) - self.affiliation[selectedMember.contactJid] = permission + permissionChangeAction(selectedMember, permission: permission) + // dismiss sheet + self.selectedMember = nil }) { label() } diff --git a/Monal/Classes/OmemoKeys.swift b/Monal/Classes/OmemoKeys.swift index 575144adcf..8621abc8dd 100644 --- a/Monal/Classes/OmemoKeys.swift +++ b/Monal/Classes/OmemoKeys.swift @@ -267,7 +267,7 @@ struct OmemoKeys: View { @State private var scannedJid : String = "" @State private var scannedFingerprints : Dictionary = [:] - @State private var contacts: [ObservableKVOWrapper] // contact list may change/be reloaded -> state + @State private var contacts: OrderedSet> // contact list may change/be reloaded -> state @State var selectedContact : ObservableKVOWrapper? // for reason why see start of body @State private var navigateToQRCodeView = false diff --git a/Monal/Classes/SwiftHelpers.swift b/Monal/Classes/SwiftHelpers.swift index 13e8b9e6b1..e002a1919a 100644 --- a/Monal/Classes/SwiftHelpers.swift +++ b/Monal/Classes/SwiftHelpers.swift @@ -69,7 +69,7 @@ class KVOObserver: NSObject { } @dynamicMemberLookup -public class ObservableKVOWrapper: ObservableObject, Hashable { +public class ObservableKVOWrapper: ObservableObject, Hashable, Equatable { public var obj: ObjType private var observedMembers: NSMutableSet = NSMutableSet() private var observers: [KVOObserver] = Array() @@ -123,15 +123,18 @@ public class ObservableKVOWrapper: ObservableObject, Hashable } } - public static func == (lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { + @inlinable + public static func ==(lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { return lhs.obj.isEqual(rhs.obj) } // see https://stackoverflow.com/a/33320737 - public static func === (lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { + @inlinable + public static func ===(lhs: ObservableKVOWrapper, rhs: ObservableKVOWrapper) -> Bool { return lhs.obj === rhs.obj } + @inlinable public func hash(into hasher: inout Hasher) { hasher.combine(self.obj.hashValue) } diff --git a/Monal/Classes/SwiftuiHelpers.swift b/Monal/Classes/SwiftuiHelpers.swift index a69d827bfd..ed257934d7 100644 --- a/Monal/Classes/SwiftuiHelpers.swift +++ b/Monal/Classes/SwiftuiHelpers.swift @@ -16,6 +16,7 @@ import PhotosUI import Combine import FLAnimatedImage +import OrderedCollections extension MLContact : Identifiable {} //make MLContact be usable in swiftui ForEach clauses @@ -464,12 +465,12 @@ class SwiftuiInterface : NSObject { } } -func getContactList(viewContact: (ObservableKVOWrapper?)) -> [ObservableKVOWrapper] { +func getContactList(viewContact: (ObservableKVOWrapper?)) -> OrderedSet> { if let contact = viewContact { if(contact.isGroup && contact.mucType == "group") { //this uses the account the muc belongs to and treats every other account to be remote, even when multiple accounts of the same monal instance are in the same group let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: contact.contactJid, forAccountId: contact.accountId)) - var contactList : [ObservableKVOWrapper] = [] + var contactList : OrderedSet> = OrderedSet() for jidDict in jidList { //jid can be participant_jid (if currently joined to muc) or member_jid (if not joined but member of muc) var jid : String? = jidDict["participant_jid"] as? String From 21f8b5670b6e61748ebb2248cea451d795d2c5cd Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 2 Mar 2024 08:57:37 +0100 Subject: [PATCH 103/115] bump pods --- Monal/Podfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Monal/Podfile.lock b/Monal/Podfile.lock index b31906d026..dd65f5952d 100644 --- a/Monal/Podfile.lock +++ b/Monal/Podfile.lock @@ -42,9 +42,9 @@ PODS: - MarqueeLabel (~> 4.3.0) - SnapKit (~> 5.6.0) - SAMKeychain (1.5.3) - - SDWebImage (5.18.11): - - SDWebImage/Core (= 5.18.11) - - SDWebImage/Core (5.18.11) + - SDWebImage (5.19.0): + - SDWebImage/Core (= 5.19.0) + - SDWebImage/Core (5.19.0) - SignalProtocolC (2.3.3) - SignalProtocolObjC (1.1.1): - SignalProtocolC (~> 2.3.3) @@ -115,7 +115,7 @@ SPEC CHECKSUMS: MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406 NotificationBannerSwift: dce54ded532b26e30cd8e7f4d80e124a0f2ba7d1 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457 + SDWebImage: 981fd7e860af070920f249fd092420006014c3eb SignalProtocolC: 8092866e45b663a6bc3e45a8d13bad2571dbf236 SignalProtocolObjC: 1beb46b1d35733e7ab96a919f88bac20ec771c73 SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25 From f6a25fbb27a49990376b6e9077aca6e64e606a5a Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 2 Mar 2024 08:57:48 +0100 Subject: [PATCH 104/115] bump crates --- rust/Cargo.lock | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2ddfe7dec9..38f2bcc7d3 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -63,9 +63,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" @@ -161,7 +161,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -328,42 +328,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" From 23f193e456b6460ce335969c068ca1a390167d22 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Sat, 2 Mar 2024 10:54:53 +0100 Subject: [PATCH 105/115] Fix build on arm --- Monal/Classes/ContactPicker.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index 4bd989f617..0001f05e4f 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -67,9 +67,17 @@ struct ContactPicker: View { } private var searchResults : OrderedSet> { - searchText.isEmpty ? self.contacts : self.contacts.filter { - ($0.contactDisplayName as String).lowercased().contains(searchText.lowercased()) || - ($0.contactJid as String).contains(searchText.lowercased()) + if searchText.isEmpty { + return self.contacts + } else { + var filteredContacts: OrderedSet> = OrderedSet() + for contact in self.contacts { + if (contact.contactDisplayName as String).lowercased().contains(searchText.lowercased()) || + (contact.contactJid as String).contains(searchText.lowercased()) { + filteredContacts.append(contact) + } + } + return filteredContacts } } From cbfcb64030ba14fb429d57ea5893968852a1606e Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 3 Mar 2024 01:20:09 +0100 Subject: [PATCH 106/115] Fix write transaction alerts --- Monal/Classes/MLSQLite.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Monal/Classes/MLSQLite.m b/Monal/Classes/MLSQLite.m index f17f812e9b..88b60fde22 100644 --- a/Monal/Classes/MLSQLite.m +++ b/Monal/Classes/MLSQLite.m @@ -385,12 +385,12 @@ -(id) idWriteTransaction:(monal_sqlite_operations_t) operations NSDate* startTime = [NSDate date]; #endif id retval = operations(); - [self endWriteTransaction]; #if !TARGET_OS_SIMULATOR NSDate* endTime = [NSDate date]; - if([endTime timeIntervalSinceDate:startTime] > 0.5) - showErrorOnAlpha(nil, @"Write transaction took %fs (longer than 0.5s): %@", (double)[endTime timeIntervalSinceDate:startTime], [NSThread callStackSymbols]); + if([endTime timeIntervalSinceDate:startTime] > 2.0) + showErrorOnAlpha(nil, @"Write transaction blocking took %fs (longer than 2.0s): %@", (double)[endTime timeIntervalSinceDate:startTime], [NSThread callStackSymbols]); #endif + [self endWriteTransaction]; return retval; } From bc3a1425d959e39a57f67e5bbe6c657280c21fc0 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 3 Mar 2024 02:35:45 +0100 Subject: [PATCH 107/115] Make text more clear, fixes #1007 --- Monal/Classes/WelcomeLogIn.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/WelcomeLogIn.swift b/Monal/Classes/WelcomeLogIn.swift index fdf2ea9952..a950e1fc32 100644 --- a/Monal/Classes/WelcomeLogIn.swift +++ b/Monal/Classes/WelcomeLogIn.swift @@ -59,9 +59,10 @@ struct WelcomeLogIn: View { } private func showTimeoutAlert() { + DDLogVerbose("Showing timeout alert...") hideLoadingOverlay(overlay) alertPrompt.title = Text("Timeout Error") - alertPrompt.message = Text("We were not able to connect your account. Please check your credentials and make sure you are connected to the internet.") + alertPrompt.message = Text("We were not able to connect your account. Please check your username and password and make sure you are connected to the internet.") showAlert = true } @@ -75,7 +76,7 @@ struct WelcomeLogIn: View { private func showLoginErrorAlert(errorMessage: String) { hideLoadingOverlay(overlay) alertPrompt.title = Text("Error") - alertPrompt.message = Text(String(format: NSLocalizedString("We were not able to connect your account. Please check your credentials and make sure you are connected to the internet.\n\nTechnical error message: %@", comment: ""), errorMessage)) + alertPrompt.message = Text(String(format: NSLocalizedString("We were not able to connect your account. Please check your username and password and make sure you are connected to the internet.\n\nTechnical error message: %@", comment: ""), errorMessage)) showAlert = true } @@ -101,7 +102,9 @@ struct WelcomeLogIn: View { self.currentTimeout = newTimeout DispatchQueue.main.asyncAfter(deadline: newTimeout) { if(newTimeout == self.currentTimeout) { + DDLogWarn("First login timeout triggered...") if(self.newAccountNo != nil) { + DDLogVerbose("Removing account...") MLXMPPManager.sharedInstance().removeAccount(forAccountNo: self.newAccountNo!) self.newAccountNo = nil } From df7c6b31867891721c4fa30a32e627cf03c79115 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 3 Mar 2024 23:53:53 +0100 Subject: [PATCH 108/115] Don't crash on SVGs --- Monal/Classes/chatViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 705ea21451..8fb9211f6f 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -2555,7 +2555,9 @@ -(UISwipeActionsConfiguration*) tableView:(UITableView*) tableView trailingSwipe -(MLBaseCell*) fileTransferCellCheckerWithInfo:(NSDictionary*)info direction:(BOOL)inDirection tableView:(UITableView*)tableView andMsg:(MLMessage*)row{ MLBaseCell *cell = nil; - if([info[@"mimeType"] hasPrefix:@"image/"]) + //don't crash on svg images not supported by UIImage + //TODO: use webview to display SVG + if([info[@"mimeType"] hasPrefix:@"image/"] && ![info[@"mimeType"] hasPrefix:@"image/svg"]) { MLChatImageCell* imageCell = (MLChatImageCell *)[self messageTableCellWithIdentifier:@"image" andInbound:inDirection fromTable:tableView]; [imageCell initCellWithMLMessage:row]; From 3649dd1062142907dae2f91cf54e150e93b7076b Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:09:52 +0100 Subject: [PATCH 109/115] cleanup code --- Monal/Classes/ContactDetails.swift | 2 +- Monal/Classes/ContactPicker.swift | 16 ++++------ Monal/Classes/EditGroupSubject.swift | 16 ++++------ Monal/Classes/MemberList.swift | 44 ++++++++++++++-------------- 4 files changed, 33 insertions(+), 45 deletions(-) diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index b1aca37275..5dbdc25359 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -369,7 +369,7 @@ struct ContactDetails: View { extension View { func navigationBarGroupEditButton(contact: ObservableKVOWrapper, isGroupModerator: Bool, account: xmpp?) -> some View { - if contact.isGroup && isGroupModerator && (account != nil && account!.accountState == xmppState.stateBound) { + if contact.isGroup && isGroupModerator && (account != nil && account!.accountState.rawValue >= xmppState.stateBound.rawValue) { return AnyView(self.toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink(destination: LazyClosureView(GroupDetailsEdit(contact: contact))) { diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index 0001f05e4f..134d61363f 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -103,7 +103,11 @@ struct ContactPicker: View { }) } } - .searchableWithiOSCheck(text: $searchText) + .ifAvailable { view in + if #available(iOS 15.0, *) { + view.searchable(text: $searchText, placement: .automatic, prompt: nil) + } + } .listStyle(.inset) .navigationBarTitle("Contact Selection", displayMode: .inline) .navigationBarBackButtonHidden(true) @@ -117,13 +121,3 @@ struct ContactPicker: View { } } } - -extension View { - func searchableWithiOSCheck(text: Binding, prompt: Text? = nil) -> some View { - if #available(iOS 15.0, *) { - return self.searchable(text: text, placement: .automatic, prompt: prompt) - } else { - return self - } - } -} diff --git a/Monal/Classes/EditGroupSubject.swift b/Monal/Classes/EditGroupSubject.swift index 092bc3fd2e..9c752f3fb0 100644 --- a/Monal/Classes/EditGroupSubject.swift +++ b/Monal/Classes/EditGroupSubject.swift @@ -8,16 +8,6 @@ import SwiftUI -extension View { - func versionConditionalLineLimit(_ limit: ClosedRange) -> some View { - if #available(iOS 16.0, *) { - return self.lineLimit(10...50) - } else { - return self - } - } -} - @available(iOS 15.0, *) struct EditGroupSubject: View { @ObservedObject var contact: ObservableKVOWrapper @@ -43,7 +33,11 @@ struct EditGroupSubject: View { Section(header: Text("Group Description")) { TextField(NSLocalizedString("Group Description (optional)", comment: "placeholder when editing a group description"), text: $subject, onEditingChanged: { isEditingSubject = $0 }) .multilineTextAlignment(.leading) - .versionConditionalLineLimit(10...50) + .ifAvailable { view in + if #available(iOS 16.0, *) { + view.lineLimit(10...50) + } + } .addClearButton(isEditing: isEditingSubject, text:$subject) } } diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index 9077b09107..d4d3bdf0be 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -45,13 +45,13 @@ struct MemberList: View { _affiliation = State(wrappedValue: affiliationTmp) } - func setAndShowAlert(title: String, description: String) { + func showAlert(title: String, description: String) { self.alertPrompt.title = Text(title) self.alertPrompt.message = Text(description) self.showAlert = true } - func ownUserHasPermissionToRemove(contact: ObservableKVOWrapper) -> Bool { + func ownUserHasAffiliationToRemove(contact: ObservableKVOWrapper) -> Bool { if contact.obj.contactJid == self.account?.connectionProperties.identity.jid { return false } @@ -83,15 +83,15 @@ struct MemberList: View { Spacer() if let contactAffiliation = self.affiliation[contact.contactJid] { if contactAffiliation == "owner" { - Text(NSLocalizedString("Owner", comment: "")) + Text(NSLocalizedString("Owner", comment: "muc affiliation")) } else if contactAffiliation == "admin" { - Text(NSLocalizedString("Admin", comment: "")) + Text(NSLocalizedString("Admin", comment: "muc affiliation")) } else if contactAffiliation == "member" { - Text(NSLocalizedString("Member", comment: "")) + Text(NSLocalizedString("Member", comment: "muc affiliation")) } else if contactAffiliation == "outcast" { - Text(NSLocalizedString("Outcast", comment: "")) + Text(NSLocalizedString("Outcast", comment: "muc affiliation")) } else { - Text(NSLocalizedString("", comment: "")) + Text(NSLocalizedString("", comment: "muc affiliation")) } } } @@ -101,23 +101,23 @@ struct MemberList: View { } }) .deleteDisabled( - !ownUserHasPermissionToRemove(contact: contact) + !ownUserHasAffiliationToRemove(contact: contact) ) } .onDelete(perform: { memberIdx in let member = self.memberList[memberIdx.first!] self.account!.mucProcessor.setAffiliation("none", ofUser: member.contactJid, inMuc: self.group.contactJid) - self.setAndShowAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) + self.showAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) self.memberList.remove(at: memberIdx.first!) }) } .onChange(of: self.memberList) { [previousMemberList = self.memberList] newMemberList in - // only handle new members (added via the contact picker + // only handle new members (added via the contact picker) for member in newMemberList { if !previousMemberList.contains(member) { - // add selected group member with role member - permissionChangeAction(member, permission: "member") + // add selected group member with affiliation member + affiliationChangeAction(member, affiliation: "member") } } } @@ -180,7 +180,7 @@ struct MemberList: View { if #available(iOS 15, *) { return Button(role: .destructive, action: { self.account!.mucProcessor.setAffiliation("none", ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) - self.setAndShowAlert(title: "Member deleted", description: selectedMember.contactJid) + self.showAlert(title: "Member deleted", description: selectedMember.contactJid) if let index = self.memberList.firstIndex(of: selectedMember) { self.memberList.remove(at: index) } @@ -193,14 +193,14 @@ struct MemberList: View { } } - func permissionChangeAction(_ selectedMember: ObservableKVOWrapper, permission: String) { - self.account!.mucProcessor.setAffiliation(permission, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) - self.affiliation[selectedMember.contactJid] = permission + func affiliationChangeAction(_ selectedMember: ObservableKVOWrapper, affiliation: String) { + self.account!.mucProcessor.setAffiliation(affiliation, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.affiliation[selectedMember.contactJid] = affiliation } - func permissionsButton(_ selectedMember: ObservableKVOWrapper, permission: String, @ViewBuilder label: () -> Label) -> some View { + func affiliationButton(_ selectedMember: ObservableKVOWrapper, affiliation: String, @ViewBuilder label: () -> Label) -> some View { return Button(action: { - permissionChangeAction(selectedMember, permission: permission) + affiliationChangeAction(selectedMember, affiliation: affiliation) // dismiss sheet self.selectedMember = nil }) { @@ -209,19 +209,19 @@ struct MemberList: View { } func makeOwner(_ selectedMember: ObservableKVOWrapper) -> some View { - return permissionsButton(selectedMember, permission: "owner", label: { + return affiliationButton(selectedMember, affiliation: "owner", label: { Text("Make owner") }) } func makeAdmin(_ selectedMember: ObservableKVOWrapper) -> some View { - return permissionsButton(selectedMember, permission: "admin", label: { + return affiliationButton(selectedMember, affiliation: "admin", label: { Text("Make admin") }) } func makeMember(_ selectedMember: ObservableKVOWrapper) -> some View { - return permissionsButton(selectedMember, permission: "member", label: { + return affiliationButton(selectedMember, affiliation: "member", label: { Text("Make member") }) } @@ -229,7 +229,7 @@ struct MemberList: View { func block(_ selectedMember: ObservableKVOWrapper) -> AnyView { if self.group.mucType != "group" { return AnyView( - permissionsButton(selectedMember, permission: "outcast", label: { + affiliationButton(selectedMember, affiliation: "outcast", label: { Text("Block grom group") }) ) From dcdfb1a73255c22ebb9d9c55713b69dd57c1de4a Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:51:56 +0100 Subject: [PATCH 110/115] --- 892 --- 6.2.0rc2 From 5a54d5620004b3359d3cd09ac35c85a7fb656f3a Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 5 Mar 2024 04:45:45 +0100 Subject: [PATCH 111/115] Fix whole bunch of ui bugs and heavily clean up code --- Monal/Classes/AddContactMenu.swift | 2 +- Monal/Classes/ChannelMemberList.swift | 23 +++---- Monal/Classes/ContactDetails.swift | 64 ++++++++--------- Monal/Classes/ContactDetailsHeader.swift | 2 +- Monal/Classes/ContactPicker.swift | 2 +- Monal/Classes/CreateGroupMenu.swift | 10 +-- Monal/Classes/DataLayer.h | 3 +- Monal/Classes/DataLayer.m | 15 +++- Monal/Classes/EditGroupName.swift | 24 +++---- Monal/Classes/EditGroupSubject.swift | 18 +++-- Monal/Classes/GroupDetailsEdit.swift | 87 +++++++++++++----------- Monal/Classes/LoadingOverlay.swift | 4 +- Monal/Classes/MLMucProcessor.m | 4 +- Monal/Classes/MemberList.swift | 44 +++++------- Monal/Classes/RegisterAccount.swift | 2 +- Monal/Classes/SwiftuiHelpers.swift | 2 +- Monal/Classes/WelcomeLogIn.swift | 2 +- 17 files changed, 153 insertions(+), 155 deletions(-) diff --git a/Monal/Classes/AddContactMenu.swift b/Monal/Classes/AddContactMenu.swift index 7bf3a6c913..fc63decb89 100644 --- a/Monal/Classes/AddContactMenu.swift +++ b/Monal/Classes/AddContactMenu.swift @@ -25,7 +25,7 @@ struct AddContactMenu: View { @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) @State private var invitationResult: [String:AnyObject]? = nil - @ObservedObject private var overlay = LoadingOverlayState() + @StateObject private var overlay = LoadingOverlayState() @State private var showQRCodeScanner = false @State private var success = false diff --git a/Monal/Classes/ChannelMemberList.swift b/Monal/Classes/ChannelMemberList.swift index 8c9cd7d172..a3cbd0444d 100644 --- a/Monal/Classes/ChannelMemberList.swift +++ b/Monal/Classes/ChannelMemberList.swift @@ -12,18 +12,18 @@ import OrderedCollections struct ChannelMemberList: View { @State private var channelMembers: OrderedDictionary - @ObservedObject var channel: ObservableKVOWrapper - private let account: xmpp? + @StateObject var channel: ObservableKVOWrapper + private let account: xmpp - init(channelContact: ObservableKVOWrapper) { - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: channelContact.accountId)! as xmpp - self.channel = channelContact; - - let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: channelContact.contactJid, forAccountId: channelContact.accountId)) + init(mucContact: ObservableKVOWrapper) { + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact.accountId)! as xmpp + _channel = StateObject(wrappedValue: mucContact) + + let jidList = Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact.contactJid, forAccountId: mucContact.accountId)) var nickSet : OrderedDictionary = OrderedDictionary() for jidDict in jidList { if let nick = jidDict["room_nick"] as? String { - nickSet.updateValue(jidDict["affiliation"]! as! String, forKey: nick) + nickSet.updateValue((jidDict["affiliation"] as? String) ?? "none", forKey:nick) } } _channelMembers = State(wrappedValue: nickSet) @@ -54,9 +54,8 @@ struct ChannelMemberList: View { } } -/*struct ChannelMemberList_Previews: PreviewProvider { +struct ChannelMemberList_Previews: PreviewProvider { static var previews: some View { - // TODO some dummy views, requires a dummy xmpp obj - // ChannelMemberList(channelContact: nil); + ChannelMemberList(mucContact:ObservableKVOWrapper(MLContact.makeDummyContact(3))); } -}*/ +} diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index 5dbdc25359..db15dc9fe4 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -12,29 +12,27 @@ import monalxmpp struct ContactDetails: View { var delegate: SheetDismisserProtocol - @ObservedObject var contact: ObservableKVOWrapper - private var account: xmpp? - @State private var showingBlockContactConfirmation: Bool = false - @State private var showingCannotBlockAlert: Bool = false - @State private var showingRemoveContactConfirmation: Bool = false - @State private var showingAddContactConfirmation: Bool = false - @State private var showingClearHistoryConfirmation: Bool = false - @State private var showingResetOmemoSessionConfirmation: Bool = false - @State private var showingCannotEncryptAlert: Bool = false - @State private var showingShouldDisableEncryptionAlert: Bool = false - @State private var isEditingNickname: Bool = false - private let isGroupModerator: Bool + private var account: xmpp + private var isGroupModerator = false + @StateObject var contact: ObservableKVOWrapper + @State private var showingBlockContactConfirmation = false + @State private var showingCannotBlockAlert = false + @State private var showingRemoveContactConfirmation = false + @State private var showingAddContactConfirmation = false + @State private var showingClearHistoryConfirmation = false + @State private var showingResetOmemoSessionConfirmation = false + @State private var showingCannotEncryptAlert = false + @State private var showingShouldDisableEncryptionAlert = false + @State private var isEditingNickname = false init(delegate: SheetDismisserProtocol, contact: ObservableKVOWrapper) { self.delegate = delegate - self.contact = contact - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId) as xmpp? + _contact = StateObject(wrappedValue: contact) + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! if contact.isGroup { - let ownRole = DataLayer.sharedInstance().getOwnRole(inGroupOrChannel: contact.obj) + let ownRole = DataLayer.sharedInstance().getOwnRole(inGroupOrChannel: contact.obj) ?? "none" self.isGroupModerator = (ownRole == "moderator") - } else { - self.isGroupModerator = false } } @@ -152,11 +150,11 @@ struct ContactDetails: View { // } if(contact.obj.isGroup && contact.obj.mucType == "group") { - NavigationLink(destination: LazyClosureView(MemberList(mucContact: contact))) { + NavigationLink(destination: LazyClosureView(MemberList(mucContact:contact))) { Text("Group Members") } } else if(contact.obj.isGroup && contact.obj.mucType == "channel") { - NavigationLink(destination: LazyClosureView(ChannelMemberList(channelContact: contact))) { + NavigationLink(destination: LazyClosureView(ChannelMemberList(mucContact:contact))) { Text("Channel Members") } } @@ -349,9 +347,7 @@ struct ContactDetails: View { .destructive( Text("Yes"), action: { - if let account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId) { - account.omemo.clearAllSessions(forJid:contact.contactJid); - } + self.account.omemo.clearAllSessions(forJid:contact.contactJid); } ) ] @@ -362,23 +358,19 @@ struct ContactDetails: View { #endif } .frame(maxWidth: .infinity, maxHeight: .infinity) - .navigationBarTitle(contact.contactDisplayName as String, displayMode: .inline) - .navigationBarGroupEditButton(contact: contact, isGroupModerator: self.isGroupModerator, account: self.account) - } -} - -extension View { - func navigationBarGroupEditButton(contact: ObservableKVOWrapper, isGroupModerator: Bool, account: xmpp?) -> some View { - if contact.isGroup && isGroupModerator && (account != nil && account!.accountState.rawValue >= xmppState.stateBound.rawValue) { - return AnyView(self.toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - NavigationLink(destination: LazyClosureView(GroupDetailsEdit(contact: contact))) { - Text("Edit") + .navigationBarTitle(contact.contactDisplayName as String, displayMode:.inline) + .applyClosure { view in + if contact.isGroup && isGroupModerator && self.account.accountState.rawValue >= xmppState.stateBound.rawValue { + view.toolbar { + ToolbarItem(placement:.navigationBarTrailing) { + let ownAffiliation = DataLayer.sharedInstance().getOwnAffiliation(inGroupOrChannel:contact.obj) ?? "none" + NavigationLink(destination:LazyClosureView(GroupDetailsEdit(contact:contact, ownAffiliation:ownAffiliation))) { + Text("Edit") + } } } - }) + } } - return AnyView(self) } } diff --git a/Monal/Classes/ContactDetailsHeader.swift b/Monal/Classes/ContactDetailsHeader.swift index e99e4227f6..e8676bd51d 100644 --- a/Monal/Classes/ContactDetailsHeader.swift +++ b/Monal/Classes/ContactDetailsHeader.swift @@ -13,7 +13,7 @@ import monalxmpp struct ContactDetailsHeader: View { var delegate: SheetDismisserProtocol - @ObservedObject var contact: ObservableKVOWrapper + @StateObject var contact: ObservableKVOWrapper @State private var navigationAction: String? var body: some View { diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index 134d61363f..42bc29f7f1 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -103,7 +103,7 @@ struct ContactPicker: View { }) } } - .ifAvailable { view in + .applyClosure { view in if #available(iOS 15.0, *) { view.searchable(text: $searchText, placement: .automatic, prompt: nil) } diff --git a/Monal/Classes/CreateGroupMenu.swift b/Monal/Classes/CreateGroupMenu.swift index e92d983f1b..d88db79a82 100644 --- a/Monal/Classes/CreateGroupMenu.swift +++ b/Monal/Classes/CreateGroupMenu.swift @@ -13,7 +13,7 @@ import monalxmpp import OrderedCollections struct CreateGroupMenu: View { - @StateObject private var appDelegate: ObservableKVOWrapper + private var appDelegate: MonalAppDelegate @State private var connectedAccounts: [xmpp] @State private var selectedAccount: xmpp? @@ -26,12 +26,12 @@ struct CreateGroupMenu: View { @State private var isEditingGroupName = false - @ObservedObject private var overlay = LoadingOverlayState() + @StateObject private var overlay = LoadingOverlayState() private var delegate: SheetDismisserProtocol init(delegate: SheetDismisserProtocol) { - _appDelegate = StateObject(wrappedValue: ObservableKVOWrapper(UIApplication.shared.delegate as! MonalAppDelegate)) + self.appDelegate = UIApplication.shared.delegate as! MonalAppDelegate self.delegate = delegate let connectedAccounts = MLXMPPManager.sharedInstance().connectedXMPP as! [xmpp] @@ -76,7 +76,7 @@ struct CreateGroupMenu: View { let groupContact = MLContact.createContact(fromJid: roomJid!, andAccountNo: self.selectedAccount!.accountNo) hideLoadingOverlay(overlay) self.delegate.dismissWithoutAnimation() - if let activeChats = self.appDelegate.obj.activeChats { + if let activeChats = self.appDelegate.activeChats { activeChats.presentChat(with:groupContact) } } else { @@ -91,7 +91,7 @@ struct CreateGroupMenu: View { let groupContact = MLContact.createContact(fromJid: roomJid!, andAccountNo: self.selectedAccount!.accountNo) hideLoadingOverlay(overlay) self.delegate.dismissWithoutAnimation() - if let activeChats = self.appDelegate.obj.activeChats { + if let activeChats = self.appDelegate.activeChats { activeChats.presentChat(with:groupContact) } } else { diff --git a/Monal/Classes/DataLayer.h b/Monal/Classes/DataLayer.h index 09d3d41faf..e223913230 100644 --- a/Monal/Classes/DataLayer.h +++ b/Monal/Classes/DataLayer.h @@ -115,7 +115,8 @@ extern NSString* const kMessageTypeFiletransfer; -(void) removeParticipant:(NSDictionary*) participant fromMuc:(NSString*) room forAccountId:(NSNumber*) accountNo; -(NSDictionary* _Nullable) getParticipantForNick:(NSString*) nick inRoom:(NSString*) room forAccountId:(NSNumber*) accountNo; -(NSArray*>*) getMembersAndParticipantsOfMuc:(NSString*) room forAccountId:(NSNumber*) accountNo; --(NSString*) getOwnRoleInGroupOrChannel:(MLContact*) contact; +-(NSString* _Nullable) getOwnAffiliationInGroupOrChannel:(MLContact*) contact; +-(NSString* _Nullable) getOwnRoleInGroupOrChannel:(MLContact*) contact; -(void) addMucFavorite:(NSString*) room forAccountId:(NSNumber*) accountNo andMucNick:(NSString* _Nullable) mucNick; -(NSString*) lastStanzaIdForMuc:(NSString* _Nonnull) room andAccount:(NSNumber* _Nonnull) accountNo; -(void) setLastStanzaId:(NSString*) lastStanzaId forMuc:(NSString* _Nonnull) room andAccount:(NSNumber* _Nonnull) accountNo; diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index d955f9b1f9..d8fea8450a 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -1011,10 +1011,19 @@ -(NSDictionary* _Nullable) getParticipantForNick:(NSString*) nick inRoom:(NSStri }]; } --(NSString*) getOwnRoleInGroupOrChannel:(MLContact*) contact +-(NSString* _Nullable) getOwnAffiliationInGroupOrChannel:(MLContact*) contact +{ + MLAssert(contact.isGroup, @"Function should only be called on a group contact"); + return [self.db idReadTransaction:^{ + NSString* retval = [self.db executeScalar:@"SELECT M.affiliation FROM muc_participants AS M INNER JOIN account AS A ON M.account_id=A.account_id WHERE M.room=? AND A.account_id=? AND (A.username || '@' || A.domain) == M.participant_jid" andArguments:@[contact.contactJid, contact.accountId]]; + if(retval == nil) + retval = [self.db executeScalar:@"SELECT M.affiliation FROM muc_members AS M INNER JOIN account AS A ON M.account_id=A.account_id WHERE M.room=? AND A.account_id=? AND (A.username || '@' || A.domain) == M.member_jid" andArguments:@[contact.contactJid, contact.accountId]]; + return retval; + }]; +} + +-(NSString* _Nullable) getOwnRoleInGroupOrChannel:(MLContact*) contact { - if(contact == nil) - return nil; MLAssert(contact.isGroup, @"Function should only be called on a group contact"); return [self.db idReadTransaction:^{ return [self.db executeScalar:@"SELECT M.role FROM muc_participants AS M INNER JOIN account AS A ON M.account_id=A.account_id WHERE M.room=? AND A.account_id=? AND (A.username || '@' || A.domain) == M.participant_jid" andArguments:@[contact.contactJid, contact.accountId]]; diff --git a/Monal/Classes/EditGroupName.swift b/Monal/Classes/EditGroupName.swift index 5d106d9450..abadd364d9 100644 --- a/Monal/Classes/EditGroupName.swift +++ b/Monal/Classes/EditGroupName.swift @@ -8,21 +8,19 @@ import SwiftUI -@available(iOS 15.0, *) struct EditGroupName: View { - @ObservedObject var contact: ObservableKVOWrapper + @StateObject var contact: ObservableKVOWrapper private let account: xmpp? - @State private var groupName: String @State private var isEditingGroupName: Bool = false - @Environment(\.dismiss) var dismiss - + @Environment(\.presentationMode) var presentationMode + init(contact: ObservableKVOWrapper) { - MLAssert(contact.isGroup) - + MLAssert(contact.isGroup, "contact must be a muc") + _groupName = State(wrappedValue: contact.obj.contactDisplayName) - self.contact = contact + _contact = StateObject(wrappedValue: contact) self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp } @@ -38,15 +36,15 @@ struct EditGroupName: View { } } .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("Abort") { - dismiss() + ToolbarItem(placement: .cancellationAction) { + Button("Abort") { + self.presentationMode.wrappedValue.dismiss() + } } - } ToolbarItem(placement: .confirmationAction) { Button("Done") { self.account!.mucProcessor.changeName(ofMuc: contact.contactJid, to: self.groupName) - dismiss() + self.presentationMode.wrappedValue.dismiss() } } } diff --git a/Monal/Classes/EditGroupSubject.swift b/Monal/Classes/EditGroupSubject.swift index 9c752f3fb0..31c47b3cbc 100644 --- a/Monal/Classes/EditGroupSubject.swift +++ b/Monal/Classes/EditGroupSubject.swift @@ -8,21 +8,19 @@ import SwiftUI -@available(iOS 15.0, *) struct EditGroupSubject: View { - @ObservedObject var contact: ObservableKVOWrapper + @StateObject var contact: ObservableKVOWrapper private let account: xmpp? - @State private var subject: String @State private var isEditingSubject: Bool = false - @Environment(\.dismiss) var dismiss + @Environment(\.presentationMode) var presentationMode init(contact: ObservableKVOWrapper) { - MLAssert(contact.isGroup) - + MLAssert(contact.isGroup, "contact must be a muc") + _subject = State(wrappedValue: contact.obj.groupSubject) - self.contact = contact + _contact = StateObject(wrappedValue: contact) self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp } @@ -33,7 +31,7 @@ struct EditGroupSubject: View { Section(header: Text("Group Description")) { TextField(NSLocalizedString("Group Description (optional)", comment: "placeholder when editing a group description"), text: $subject, onEditingChanged: { isEditingSubject = $0 }) .multilineTextAlignment(.leading) - .ifAvailable { view in + .applyClosure { view in if #available(iOS 16.0, *) { view.lineLimit(10...50) } @@ -46,13 +44,13 @@ struct EditGroupSubject: View { .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Abort") { - dismiss() + self.presentationMode.wrappedValue.dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("Done") { self.account!.mucProcessor.changeSubject(ofMuc: contact.contactJid, to: self.subject) - dismiss() + self.presentationMode.wrappedValue.dismiss() } } } diff --git a/Monal/Classes/GroupDetailsEdit.swift b/Monal/Classes/GroupDetailsEdit.swift index a7c2e648f3..36fb1b39b9 100644 --- a/Monal/Classes/GroupDetailsEdit.swift +++ b/Monal/Classes/GroupDetailsEdit.swift @@ -10,45 +10,49 @@ import SwiftUI import _PhotosUI_SwiftUI struct GroupDetailsEdit: View { - @ObservedObject var contact: ObservableKVOWrapper - private let account: xmpp? - + @StateObject var contact: ObservableKVOWrapper @State private var showingSheetEditName = false @State private var showingSheetEditSubject = false @State private var inputImage: UIImage? @State private var showingImagePicker = false - @ObservedObject private var overlay = LoadingOverlayState() + @StateObject private var overlay = LoadingOverlayState() + private let account: xmpp + private let ownAffiliation: String? - init(contact: ObservableKVOWrapper) { + init(contact: ObservableKVOWrapper, ownAffiliation: String?) { MLAssert(contact.isGroup) - - self.contact = contact - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp + + _contact = StateObject(wrappedValue: contact) _inputImage = State(initialValue: contact.avatar) + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: contact.accountId)! as xmpp + self.ownAffiliation = ownAffiliation } var body: some View { Form { - Section { - HStack { - Spacer() - Image(uiImage: contact.avatar) - .resizable() - .scaledToFit() - .accessibilityLabel((contact.obj.mucType == "group") ? "Group Avatar" : "Channel Avatar") - .frame(width: 150, height: 150, alignment: .center) - .shadow(radius: 7) - .onTapGesture { - showingImagePicker = true - } - Spacer() - } - .sheet(isPresented:$showingImagePicker) { - ImagePicker(image:$inputImage) + if ownAffiliation == "owner" { + Section { + HStack { + Spacer() + Image(uiImage: contact.avatar) + .resizable() + .scaledToFit() + .accessibilityLabel((contact.obj.mucType == "group") ? "Group Avatar" : "Channel Avatar") + .frame(width: 150, height: 150, alignment: .center) + .shadow(radius: 7) + .onTapGesture { + showingImagePicker = true + } + Spacer() + } + .sheet(isPresented:$showingImagePicker) { + ImagePicker(image:$inputImage) + } } } + Section { - if #available(iOS 15.0, *) { + if ownAffiliation == "owner" { Button(action: { showingSheetEditName.toggle() }) { @@ -61,26 +65,31 @@ struct GroupDetailsEdit: View { .sheet(isPresented: $showingSheetEditName) { LazyClosureView(EditGroupName(contact: contact)) } - Button(action: { - showingSheetEditSubject.toggle() - }) { - HStack { - Image(systemName: "pencil") - Text((contact.obj.mucType == "group") ? "Group description" : "Channel description") - Spacer() + } + + Button(action: { + showingSheetEditSubject.toggle() + }) { + HStack { + Image(systemName: "pencil") + if contact.obj.mucType == "group" { + Text("Group description") + } else { + Text("Channel description") } - } - .sheet(isPresented: $showingSheetEditSubject) { - LazyClosureView(EditGroupSubject(contact: contact)) + Spacer() } } + .sheet(isPresented: $showingSheetEditSubject) { + LazyClosureView(EditGroupSubject(contact: contact)) + } } } .addLoadingOverlay(overlay) .navigationTitle((contact.obj.mucType == "group") ? "Edit group" : "Edit channel") .onChange(of:inputImage) { _ in showLoadingOverlay(overlay, headline: NSLocalizedString("Uploading image...", comment: "")) - self.account!.mucProcessor.publishAvatar(inputImage, forMuc: contact.contactJid) + self.account.mucProcessor.publishAvatar(inputImage, forMuc: contact.contactJid) } .onChange(of:contact.avatar as UIImage) { _ in hideLoadingOverlay(overlay) @@ -88,6 +97,8 @@ struct GroupDetailsEdit: View { } } -#Preview { - GroupDetailsEdit(contact:ObservableKVOWrapper(MLContact.makeDummyContact(0))) +struct GroupDetailsEdit_Previews: PreviewProvider { + static var previews: some View { + GroupDetailsEdit(contact:ObservableKVOWrapper(MLContact.makeDummyContact(0)), ownAffiliation:"owner") + } } diff --git a/Monal/Classes/LoadingOverlay.swift b/Monal/Classes/LoadingOverlay.swift index eed244a60f..bb8f0d4f62 100644 --- a/Monal/Classes/LoadingOverlay.swift +++ b/Monal/Classes/LoadingOverlay.swift @@ -85,8 +85,8 @@ func hideLoadingOverlay(_ overlay: LoadingOverlayState) { } struct LoadingOverlay_Previews: PreviewProvider { - @ObservedObject static var overlay1 = LoadingOverlayState(enabled:true, headline:AnyView(Text("Loading")), description:AnyView(Text("More info?"))) - @ObservedObject static var overlay2 = LoadingOverlayState(enabled:true, headline:AnyView(Text("Loading")), description:AnyView(HStack { + @StateObject static var overlay1 = LoadingOverlayState(enabled:true, headline:AnyView(Text("Loading")), description:AnyView(Text("More info?"))) + @StateObject static var overlay2 = LoadingOverlayState(enabled:true, headline:AnyView(Text("Loading")), description:AnyView(HStack { Image(systemName: "checkmark") Text("Doing a lot of work...") })) diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 0558daf947..404b8ddaf1 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -301,7 +301,7 @@ -(void) processPresence:(XMPPPresence*) presenceNode item[@"nick"] = presenceNode.fromResource; //handle participant updates - if([presenceNode check:@"/"]) + if([presenceNode check:@"/"] || item[@"affiliation"] == nil) [[DataLayer sharedInstance] removeParticipant:item fromMuc:presenceNode.fromUser forAccountId:_account.accountNo]; else [[DataLayer sharedInstance] addParticipant:item toMuc:presenceNode.fromUser forAccountId:_account.accountNo]; @@ -396,7 +396,7 @@ -(void) handleMembersListUpdate:(NSArray*) items forMuc:(NSString BOOL isTypeGroup = [[[DataLayer sharedInstance] getMucTypeOfRoom:mucJid andAccount:_account.accountNo] isEqualToString:@"group"]; #endif - if([@"none" isEqualToString:item[@"affiliation"]]) + if(item[@"affiliation"] == nil || [@"none" isEqualToString:item[@"affiliation"]]) { DDLogVerbose(@"Removing member '%@' from muc '%@'...", item[@"jid"], mucJid); [[DataLayer sharedInstance] removeMember:item fromMuc:mucJid forAccountId:_account.accountNo]; diff --git a/Monal/Classes/MemberList.swift b/Monal/Classes/MemberList.swift index d4d3bdf0be..052efb1d53 100644 --- a/Monal/Classes/MemberList.swift +++ b/Monal/Classes/MemberList.swift @@ -11,36 +11,27 @@ import monalxmpp import OrderedCollections struct MemberList: View { + private let account: xmpp + private let ownAffiliation: String; + @StateObject var group: ObservableKVOWrapper @State private var memberList: OrderedSet> @State private var affiliation: Dictionary - @ObservedObject var group: ObservableKVOWrapper - private let account: xmpp? - private var ownAffiliation: String = "none"; - @State private var openAccountSelection : Bool = false - @State private var showAlert = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) - @State private var selectedMember: MLContact? - init(mucContact: ObservableKVOWrapper?) { - self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact!.accountId)! as xmpp - self.group = mucContact!; + init(mucContact: ObservableKVOWrapper) { + self.account = MLXMPPManager.sharedInstance().getConnectedAccount(forID: mucContact.accountId)! as xmpp + _group = StateObject(wrappedValue: mucContact) _memberList = State(wrappedValue: getContactList(viewContact: mucContact)) + self.ownAffiliation = DataLayer.sharedInstance().getOwnAffiliation(inGroupOrChannel:mucContact.obj) ?? "none" var affiliationTmp = Dictionary() - for memberInfo in Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact!.contactJid, forAccountId: self.account!.accountNo)) { - var jid : String? = memberInfo["participant_jid"] as? String - if(jid == nil) { - jid = memberInfo["member_jid"] as? String - } - if(jid == nil) { + for memberInfo in Array(DataLayer.sharedInstance().getMembersAndParticipants(ofMuc: mucContact.contactJid, forAccountId: self.account.accountNo)) { + guard let jid = memberInfo["participant_jid"] as? String ?? memberInfo["member_jid"] as? String else { continue } - if(jid == self.account?.connectionProperties.identity.jid) { - self.ownAffiliation = memberInfo["affiliation"]! as! String - } - affiliationTmp.updateValue(memberInfo["affiliation"]! as! String, forKey: jid!) + affiliationTmp.updateValue((memberInfo["affiliation"] as? String) ?? "none", forKey: jid) } _affiliation = State(wrappedValue: affiliationTmp) } @@ -52,7 +43,7 @@ struct MemberList: View { } func ownUserHasAffiliationToRemove(contact: ObservableKVOWrapper) -> Bool { - if contact.obj.contactJid == self.account?.connectionProperties.identity.jid { + if contact.obj.contactJid == self.account.connectionProperties.identity.jid { return false } if let contactAffiliation = self.affiliation[contact.contactJid] { @@ -69,7 +60,7 @@ struct MemberList: View { List { Section(header: Text(self.group.obj.contactDisplayName)) { if self.ownAffiliation == "owner" || self.ownAffiliation == "admin" { - NavigationLink(destination: LazyClosureView(ContactPicker(account: self.account!, selectedContacts: $memberList, existingMembers: self.memberList)), label: { + NavigationLink(destination: LazyClosureView(ContactPicker(account: self.account, selectedContacts: $memberList, existingMembers: self.memberList)), label: { Text("Add Group Members") }) } @@ -96,7 +87,7 @@ struct MemberList: View { } } .onTapGesture(perform: { - if(contact.obj.contactJid != self.account?.connectionProperties.identity.jid) { + if contact.obj.contactJid != self.account.connectionProperties.identity.jid { self.selectedMember = contact.obj } }) @@ -106,7 +97,7 @@ struct MemberList: View { } .onDelete(perform: { memberIdx in let member = self.memberList[memberIdx.first!] - self.account!.mucProcessor.setAffiliation("none", ofUser: member.contactJid, inMuc: self.group.contactJid) + self.account.mucProcessor.setAffiliation("none", ofUser: member.contactJid, inMuc: self.group.contactJid) self.showAlert(title: "Member deleted", description: self.memberList[memberIdx.first!].contactJid) self.memberList.remove(at: memberIdx.first!) @@ -179,7 +170,7 @@ struct MemberList: View { func removeUserButton(_ selectedMember: ObservableKVOWrapper) -> some View { if #available(iOS 15, *) { return Button(role: .destructive, action: { - self.account!.mucProcessor.setAffiliation("none", ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.account.mucProcessor.setAffiliation("none", ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) self.showAlert(title: "Member deleted", description: selectedMember.contactJid) if let index = self.memberList.firstIndex(of: selectedMember) { self.memberList.remove(at: index) @@ -194,7 +185,7 @@ struct MemberList: View { } func affiliationChangeAction(_ selectedMember: ObservableKVOWrapper, affiliation: String) { - self.account!.mucProcessor.setAffiliation(affiliation, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) + self.account.mucProcessor.setAffiliation(affiliation, ofUser: selectedMember.contactJid, inMuc: self.group.contactJid) self.affiliation[selectedMember.contactJid] = affiliation } @@ -241,7 +232,6 @@ struct MemberList: View { struct MemberList_Previews: PreviewProvider { static var previews: some View { - // TODO some dummy views, requires a dummy xmpp obj - MemberList(mucContact: nil); + MemberList(mucContact:ObservableKVOWrapper(MLContact.makeDummyContact(2))); } } diff --git a/Monal/Classes/RegisterAccount.swift b/Monal/Classes/RegisterAccount.swift index dda5c9885a..f3bc791d4b 100644 --- a/Monal/Classes/RegisterAccount.swift +++ b/Monal/Classes/RegisterAccount.swift @@ -55,7 +55,7 @@ struct RegisterAccount: View { @State private var captchaText: String = "" @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) - @ObservedObject private var overlay = LoadingOverlayState() + @StateObject private var overlay = LoadingOverlayState() @State private var currentTimeout : DispatchTime? = nil @State private var showWebView = false diff --git a/Monal/Classes/SwiftuiHelpers.swift b/Monal/Classes/SwiftuiHelpers.swift index ed257934d7..4f25c63345 100644 --- a/Monal/Classes/SwiftuiHelpers.swift +++ b/Monal/Classes/SwiftuiHelpers.swift @@ -334,7 +334,7 @@ extension View { /// - Parameters: /// - transform: The transform to apply to the source `View`. /// - Returns: The view transformed by the transform. - func ifAvailable(@ViewBuilder _ transform: (Self) -> Content) -> some View { + func applyClosure(@ViewBuilder _ transform: (Self) -> Content) -> some View { transform(self) } } diff --git a/Monal/Classes/WelcomeLogIn.swift b/Monal/Classes/WelcomeLogIn.swift index a950e1fc32..765852a813 100644 --- a/Monal/Classes/WelcomeLogIn.swift +++ b/Monal/Classes/WelcomeLogIn.swift @@ -30,7 +30,7 @@ struct WelcomeLogIn: View { @State private var isLoadingOmemoBundles = false @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) - @ObservedObject private var overlay = LoadingOverlayState() + @StateObject private var overlay = LoadingOverlayState() #if IS_ALPHA let appLogoId = "AlphaAppLogo" From 8a735c4124519a2e34ced8ba8d8af174f15a69a2 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:35:56 +0100 Subject: [PATCH 112/115] Fix bugs introduced by fixing bugs --- Monal/Classes/ContactDetails.swift | 2 ++ Monal/Classes/ContactPicker.swift | 2 ++ Monal/Classes/EditGroupSubject.swift | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Monal/Classes/ContactDetails.swift b/Monal/Classes/ContactDetails.swift index db15dc9fe4..1f3af26879 100644 --- a/Monal/Classes/ContactDetails.swift +++ b/Monal/Classes/ContactDetails.swift @@ -369,6 +369,8 @@ struct ContactDetails: View { } } } + } else { + view } } } diff --git a/Monal/Classes/ContactPicker.swift b/Monal/Classes/ContactPicker.swift index 42bc29f7f1..a58279fde0 100644 --- a/Monal/Classes/ContactPicker.swift +++ b/Monal/Classes/ContactPicker.swift @@ -106,6 +106,8 @@ struct ContactPicker: View { .applyClosure { view in if #available(iOS 15.0, *) { view.searchable(text: $searchText, placement: .automatic, prompt: nil) + } else { + view } } .listStyle(.inset) diff --git a/Monal/Classes/EditGroupSubject.swift b/Monal/Classes/EditGroupSubject.swift index 31c47b3cbc..7dd8e816ec 100644 --- a/Monal/Classes/EditGroupSubject.swift +++ b/Monal/Classes/EditGroupSubject.swift @@ -34,6 +34,8 @@ struct EditGroupSubject: View { .applyClosure { view in if #available(iOS 16.0, *) { view.lineLimit(10...50) + } else { + view } } .addClearButton(isEditing: isEditingSubject, text:$subject) From cb548c1020e281272fb70dddc5e5e680d364e82e Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Tue, 5 Mar 2024 17:37:38 +0100 Subject: [PATCH 113/115] Reimplement disco info query deduplication --- Monal/Classes/MLIQProcessor.m | 1 + Monal/Classes/xmpp.h | 2 ++ Monal/Classes/xmpp.m | 35 ++++++++++++++++++++++++++++------- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index 5e7b19c30b..e1a91e63ec 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -618,6 +618,7 @@ +(BOOL) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode NSArray* forms = [iqNode find:@"{http://jabber.org/protocol/disco#info}query/{jabber:x:data}x"]; NSString* ver = [HelperTools getEntityCapsHashForIdentities:identities andFeatures:features andForms:forms]; [[DataLayer sharedInstance] setCaps:features forVer:ver onAccountNo:account.accountNo]; + [account markCapsQueryCompleteFor:ver]; //send out kMonalContactRefresh notification [[MLNotificationQueue currentQueue] postNotificationName:kMonalContactRefresh object:account userInfo:@{ diff --git a/Monal/Classes/xmpp.h b/Monal/Classes/xmpp.h index 3114dc3edf..158380c27e 100644 --- a/Monal/Classes/xmpp.h +++ b/Monal/Classes/xmpp.h @@ -244,6 +244,8 @@ typedef void (^monal_iq_handler_t)(XMPPIQ* _Nullable); -(void) createInvitationWithCompletion:(monal_id_block_t) completion; +-(void) markCapsQueryCompleteFor:(NSString*) ver; + @end NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 00276c6582..59c7ae6f46 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -49,7 +49,7 @@ @import AVFoundation; @import WebRTC; -#define STATE_VERSION 11 +#define STATE_VERSION 12 #define CONNECT_TIMEOUT 7.0 #define IQ_TIMEOUT 60.0 NSString* const kQueueID = @"queueID"; @@ -102,6 +102,7 @@ @interface xmpp() NSMutableArray* _smacksAckHandler; NSMutableDictionary* _iqHandlers; NSMutableArray* _reconnectionHandlers; + NSMutableSet* _runningCapsQueries; NSMutableDictionary* _runningMamQueries; BOOL _SRVDiscoveryDone; BOOL _startTLSComplete; @@ -258,6 +259,7 @@ -(void) setupObjects _iqHandlers = [NSMutableDictionary new]; _reconnectionHandlers = [NSMutableArray new]; _mamPageArrays = [NSMutableDictionary new]; + _runningCapsQueries = [NSMutableSet new]; _runningMamQueries = [NSMutableDictionary new]; _inCatchup = [NSMutableDictionary new]; _pipeliningState = kPipelinedNothing; @@ -1932,7 +1934,7 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR BOOL shouldQueryCaps = NO; if(![@"sha-1" isEqualToString:[presenceNode findFirst:@"{http://jabber.org/protocol/caps}c@hash"]]) { - DDLogWarn(@"Unknown caps hash algo '%@', querying disco without checking hash!", [presenceNode findFirst:@"{http://jabber.org/protocol/caps}c@hash"]); + DDLogWarn(@"Unknown caps hash algo '%@', requesting disco query without checking hash!", [presenceNode findFirst:@"{http://jabber.org/protocol/caps}c@hash"]); shouldQueryCaps = YES; } else @@ -1943,17 +1945,24 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR if(![[DataLayer sharedInstance] getCapsforVer:newVer onAccountNo:self.accountNo]) { - DDLogInfo(@"Presence included unknown caps hash %@, querying disco", newVer); + DDLogInfo(@"Presence included unknown caps hash %@, requesting disco query", newVer); shouldQueryCaps = YES; } } if(shouldQueryCaps) { - XMPPIQ* discoInfo = [[XMPPIQ alloc] initWithType:kiqGetType]; - [discoInfo setiqTo:presenceNode.from]; - [discoInfo setDiscoInfoNode]; - [self sendIq:discoInfo withHandler:$newHandler(MLIQProcessor, handleEntityCapsDisco)]; + if([_runningCapsQueries containsObject:newVer]) + DDLogInfo(@"Presence included unknown caps hash %@, but disco query already running, not querying again", newVer); + else + { + DDLogInfo(@"Querying disco for caps hash: %@", newVer); + XMPPIQ* discoInfo = [[XMPPIQ alloc] initWithType:kiqGetType]; + [discoInfo setiqTo:presenceNode.from]; + [discoInfo setDiscoInfoNode]; + [self sendIq:discoInfo withHandler:$newHandler(MLIQProcessor, handleEntityCapsDisco)]; + [_runningCapsQueries addObject:newVer]; + } } } @@ -3416,6 +3425,7 @@ -(void) realPersistState [values setObject:[self.pubsub getInternalData] forKey:@"pubsubData"]; [values setObject:[self.mucProcessor getInternalState] forKey:@"mucState"]; + [values setObject:[self->_runningCapsQueries copy] forKey:@"runningCapsQueries"]; [values setObject:[self->_runningMamQueries copy] forKey:@"runningMamQueries"]; [values setObject:[NSNumber numberWithBool:self->_loggedInOnce] forKey:@"loggedInOnce"]; [values setObject:[NSNumber numberWithBool:self.connectionProperties.usingCarbons2] forKey:@"usingCarbons2"]; @@ -3671,6 +3681,9 @@ -(void) realReadState if([dic objectForKey:@"mucState"]) [self.mucProcessor setInternalState:[dic objectForKey:@"mucState"]]; + if([dic objectForKey:@"runningCapsQueries"]) + _runningCapsQueries = [[dic objectForKey:@"runningCapsQueries"] mutableCopy]; + if([dic objectForKey:@"runningMamQueries"]) _runningMamQueries = [[dic objectForKey:@"runningMamQueries"] mutableCopy]; @@ -3952,6 +3965,9 @@ -(void) initSession //clear list of running mam queries _runningMamQueries = [NSMutableDictionary new]; + //clear list of running caps queries + _runningCapsQueries = [NSMutableSet new]; + //clear old catchup state (technically all stanzas still in delayedMessageStanzas could have also been //in the parseQueue in the last run and deleted there) //--> no harm in deleting them when starting a new session (but DON'T DELETE them when resuming the old smacks session) @@ -5288,6 +5304,11 @@ -(void) removeFromServerWithCompletion:(void (^)(NSString* _Nullable error)) com }]; } +-(void) markCapsQueryCompleteFor:(NSString*) ver +{ + [_runningCapsQueries removeObject:ver]; +} + -(void) publishRosterName:(NSString* _Nullable) rosterName { DDLogInfo(@"Publishing own nickname: '%@'", rosterName); From e47afc602de4c64b10c708e4778ead5e98a1761a Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:38:33 +0100 Subject: [PATCH 114/115] --- 893 --- 6.2.0b3 From faab2944b5a389cdb6ac74957ef781cd02aa3c24 Mon Sep 17 00:00:00 2001 From: Friedrich Altheide <11352905+FriedrichAltheide@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:55:55 +0100 Subject: [PATCH 115/115] disable enableSwapOfCxaThrow on catalyst due to crash since 14.4 --- Monal/Classes/HelperTools.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index e920228f8b..5314446f9e 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -1788,8 +1788,10 @@ +(void) installCrashHandler handler.monitoring = KSCrashMonitorTypeProductionSafe; //KSCrashMonitorTypeAll handler.onCrash = crash_callback; //this can trigger crashes on macos < 13 (e.g. mac catalyst < 16) (and possibly ios < 16) - if(@available(iOS 16.0, macCatalyst 16.0, *)) +#if !TARGET_OS_MACCATALYST + if(@available(iOS 16.0, *)) [handler enableSwapOfCxaThrow]; +#endif handler.searchQueueNames = NO; //this is not async safe and can crash :( handler.introspectMemory = YES; handler.addConsoleLogToReport = YES;