From ba622764c1513e0e302a1f169b08d93c08c1eb13 Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Mon, 24 Feb 2025 21:24:09 +0100 Subject: [PATCH 1/3] fix(mentions): Highlight group and team mentions Signed-off-by: Ivan Sein --- NextcloudTalk/NCAPIControllerExtensions.swift | 39 +++++++++++++++ NextcloudTalk/NCConnectionController.m | 3 ++ NextcloudTalk/NCDatabaseManager.m | 2 +- NextcloudTalk/NCMessageParameter.m | 11 +++-- NextcloudTalk/NCSettingsController.h | 1 + NextcloudTalk/NCSettingsController.m | 49 +++++++++++++++++++ NextcloudTalk/TalkAccount.h | 2 + 7 files changed, 103 insertions(+), 4 deletions(-) diff --git a/NextcloudTalk/NCAPIControllerExtensions.swift b/NextcloudTalk/NCAPIControllerExtensions.swift index 2af196d82..33a7970c4 100644 --- a/NextcloudTalk/NCAPIControllerExtensions.swift +++ b/NextcloudTalk/NCAPIControllerExtensions.swift @@ -703,4 +703,43 @@ import Foundation } } } + + // MARK: - Groups & Teams + + func getUserGroups(forAccount account: TalkAccount, completionBlock: @escaping (_ groupIds: [String]?, _ error: Error?) -> Void) { + guard let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager + else { + completionBlock(nil, NSError(domain: "", code: 0, userInfo: nil)) + return + } + + let urlString = "\(account.server)/ocs/v2.php/cloud/users/\(account.userId)/groups" + + apiSessionManager.getOcs(urlString, account: account) { ocsResponse, ocsError in + if ocsError?.error == nil, let groupdIds = ocsResponse?.dataDict?["groups"] as? [String] { + completionBlock(groupdIds, nil) + } else { + completionBlock(nil, ocsError?.error) + } + } + } + + func getUserTeams(forAccount account: TalkAccount, completionBlock: @escaping (_ teamIds: [String]?, _ error: Error?) -> Void) { + guard let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager + else { + completionBlock(nil, NSError(domain: "", code: 0, userInfo: nil)) + return + } + + let urlString = "\(account.server)/ocs/v2.php/apps/circles/probecircles" + + apiSessionManager.getOcs(urlString, account: account) { ocsResponse, ocsError in + if ocsError?.error == nil, let teamsDicts = ocsResponse?.dataArrayDict { + let teamIds = teamsDicts.compactMap { $0["id"] as? String } + completionBlock(teamIds, nil) + } else { + completionBlock(nil, ocsError?.error) + } + } + } } diff --git a/NextcloudTalk/NCConnectionController.m b/NextcloudTalk/NCConnectionController.m index f9d03ce6a..fda02d97d 100644 --- a/NextcloudTalk/NCConnectionController.m +++ b/NextcloudTalk/NCConnectionController.m @@ -121,6 +121,9 @@ - (void)checkAppState }]; }]; } else { + // Fetch additional data asynchronously. + // We set the app as ready, so we don’t need to wait for this to complete. + [[NCSettingsController sharedInstance] getUserGroupsAndTeamsForAccountId:activeAccount.accountId]; [self setAppState:kAppStateReady]; } diff --git a/NextcloudTalk/NCDatabaseManager.m b/NextcloudTalk/NCDatabaseManager.m index f42bba441..1d336b6bf 100644 --- a/NextcloudTalk/NCDatabaseManager.m +++ b/NextcloudTalk/NCDatabaseManager.m @@ -16,7 +16,7 @@ NSString *const kTalkDatabaseFolder = @"Library/Application Support/Talk"; NSString *const kTalkDatabaseFileName = @"talk.realm"; -uint64_t const kTalkDatabaseSchemaVersion = 75; +uint64_t const kTalkDatabaseSchemaVersion = 76; NSString * const kCapabilitySystemMessages = @"system-messages"; NSString * const kCapabilityNotificationLevels = @"notification-levels"; diff --git a/NextcloudTalk/NCMessageParameter.m b/NextcloudTalk/NCMessageParameter.m index 753bda6eb..276c83d8f 100644 --- a/NextcloudTalk/NCMessageParameter.m +++ b/NextcloudTalk/NCMessageParameter.m @@ -51,10 +51,15 @@ - (instancetype)initWithDictionary:(NSDictionary *)parameterDict - (BOOL)shouldBeHighlighted { - // Own mentions - // Call mentions TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; - return ([_type isEqualToString:@"user"] && [activeAccount.userId isEqualToString:_parameterId]) || [_type isEqualToString:@"call"]; + BOOL ownMention = [_type isEqualToString:@"user"] && [activeAccount.userId isEqualToString:_parameterId]; + BOOL callMention = [_type isEqualToString:@"call"]; + NSArray *userGroups = [activeAccount.groupIds valueForKey:@"self"]; + BOOL groupMention = [_type isEqualToString:@"user-group"] && [userGroups containsObject:_mention.id]; + NSArray *userTeams = [activeAccount.teamIds valueForKey:@"self"]; + BOOL teamMention = [_type isEqualToString:@"circle"] && [userTeams containsObject:_mention.id]; + + return ownMention || callMention || groupMention || teamMention; } - (BOOL)isMention diff --git a/NextcloudTalk/NCSettingsController.h b/NextcloudTalk/NCSettingsController.h index ec89340c3..62d695df0 100644 --- a/NextcloudTalk/NCSettingsController.h +++ b/NextcloudTalk/NCSettingsController.h @@ -56,6 +56,7 @@ typedef NS_ENUM(NSInteger, NCPreferredFileSorting) { - (void)addNewAccountForUser:(NSString *)user withToken:(NSString *)token inServer:(NSString *)server; - (void)setActiveAccountWithAccountId:(NSString *)accountId; - (void)getUserProfileForAccountId:(NSString *)accountId withCompletionBlock:(UpdatedProfileCompletionBlock _Nonnull)block; +- (void)getUserGroupsAndTeamsForAccountId:(NSString *)accountId; - (void)logoutAccountWithAccountId:(NSString *)accountId withCompletionBlock:(LogoutCompletionBlock)block; - (void)getCapabilitiesForAccountId:(NSString *)accountId withCompletionBlock:(GetCapabilitiesCompletionBlock)block; - (void)updateSignalingConfigurationForAccountId:(NSString * _Nonnull)accountId withCompletionBlock:(UpdateSignalingConfigCompletionBlock _Nonnull)block; diff --git a/NextcloudTalk/NCSettingsController.m b/NextcloudTalk/NCSettingsController.m index b0d23c734..6b6bf4faf 100644 --- a/NextcloudTalk/NCSettingsController.m +++ b/NextcloudTalk/NCSettingsController.m @@ -416,6 +416,55 @@ - (void)getUserProfileForAccountId:(NSString *)accountId withCompletionBlock:(Up }]; } +- (void)getUserGroupsAndTeamsForAccountId:(NSString *)accountId +{ + TalkAccount *account = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:accountId]; + + if (!account) { + return; + } + + [[NCAPIController sharedInstance] getUserGroupsForAccount:account completionBlock:^(NSArray * _Nullable groupIds, NSError * _Nullable error) { + if (!error) { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + + NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; + TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; + + if (!managedActiveAccount) { + return; + } + + managedActiveAccount.groupIds = groupIds; + + [realm commitWriteTransaction]; + } else { + NSLog(@"Error while getting user's groups"); + } + }]; + + [[NCAPIController sharedInstance] getUserTeamsForAccount:account completionBlock:^(NSArray * _Nullable teamIds, NSError * _Nullable error) { + if (!error) { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + + NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; + TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; + + if (!managedActiveAccount) { + return; + } + + managedActiveAccount.teamIds = teamIds; + + [realm commitWriteTransaction]; + } else { + NSLog(@"Error while getting user' teams"); + } + }]; +} + - (void)logoutAccountWithAccountId:(NSString *)accountId withCompletionBlock:(LogoutCompletionBlock)block { TalkAccount *removingAccount = [[NCDatabaseManager sharedInstance] talkAccountForAccountId:accountId]; diff --git a/NextcloudTalk/TalkAccount.h b/NextcloudTalk/TalkAccount.h index a4f3ea9c7..01eaa3b5c 100644 --- a/NextcloudTalk/TalkAccount.h +++ b/NextcloudTalk/TalkAccount.h @@ -43,6 +43,8 @@ NS_ASSUME_NONNULL_BEGIN @property NSString *lastNotificationETag; @property NSInteger pendingFederationInvitations; @property NSString *frequentlyUsedEmojisJSONString; +@property RLMArray *groupIds; +@property RLMArray *teamIds; @end From 5e981ba22c147044b90f60c7f872af2dcfa2685e Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Tue, 25 Feb 2025 17:29:47 +0100 Subject: [PATCH 2/3] fix: Encode userId for the url string Signed-off-by: Ivan Sein --- NextcloudTalk/NCAPIController.m | 3 ++- NextcloudTalk/NCAPIControllerExtensions.swift | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/NextcloudTalk/NCAPIController.m b/NextcloudTalk/NCAPIController.m index 59e41bddc..ddc47fd83 100644 --- a/NextcloudTalk/NCAPIController.m +++ b/NextcloudTalk/NCAPIController.m @@ -2461,7 +2461,8 @@ - (NSURLSessionDataTask *)getUserProfileEditableFieldsForAccount:(TalkAccount *) - (NSURLSessionDataTask *)setUserProfileField:(NSString *)field withValue:(NSString*)value forAccount:(TalkAccount *)account withCompletionBlock:(SetUserProfileFieldCompletionBlock)block { - NSString *URLString = [NSString stringWithFormat:@"%@/ocs/v2.php/cloud/users/%@", account.server, account.userId]; + NSString *encodedUserId = [account.userId stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]; + NSString *URLString = [NSString stringWithFormat:@"%@/ocs/v2.php/cloud/users/%@", account.server, encodedUserId]; NSDictionary *parameters = @{@"format" : @"json", @"key" : field, @"value" : value}; diff --git a/NextcloudTalk/NCAPIControllerExtensions.swift b/NextcloudTalk/NCAPIControllerExtensions.swift index 33a7970c4..7b341c474 100644 --- a/NextcloudTalk/NCAPIControllerExtensions.swift +++ b/NextcloudTalk/NCAPIControllerExtensions.swift @@ -707,13 +707,14 @@ import Foundation // MARK: - Groups & Teams func getUserGroups(forAccount account: TalkAccount, completionBlock: @escaping (_ groupIds: [String]?, _ error: Error?) -> Void) { - guard let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager + guard let encodedUserId = account.userId.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager else { completionBlock(nil, NSError(domain: "", code: 0, userInfo: nil)) return } - let urlString = "\(account.server)/ocs/v2.php/cloud/users/\(account.userId)/groups" + let urlString = "\(account.server)/ocs/v2.php/cloud/users/\(encodedUserId)/groups" apiSessionManager.getOcs(urlString, account: account) { ocsResponse, ocsError in if ocsError?.error == nil, let groupdIds = ocsResponse?.dataDict?["groups"] as? [String] { From 4f62050a7adffcd7be8c4be53f7e658bd243e2af Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Tue, 25 Feb 2025 18:05:16 +0100 Subject: [PATCH 3/3] fix: Use transactionWithBlock to avoid leaving realm in a write transaction Signed-off-by: Ivan Sein --- NextcloudTalk/NCSettingsController.m | 96 +++++++++++++--------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/NextcloudTalk/NCSettingsController.m b/NextcloudTalk/NCSettingsController.m index 6b6bf4faf..2dc1c2d97 100644 --- a/NextcloudTalk/NCSettingsController.m +++ b/NextcloudTalk/NCSettingsController.m @@ -374,41 +374,39 @@ - (void)getUserProfileForAccountId:(NSString *)accountId withCompletionBlock:(Up email = @""; } RLMRealm *realm = [RLMRealm defaultRealm]; - [realm beginWriteTransaction]; - - NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; - TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; - - if (!managedActiveAccount) { - NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; - block(error); - - return; - } + [realm transactionWithBlock:^{ + NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; + TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; - managedActiveAccount.userId = [userProfile objectForKey:kUserProfileUserId]; - // "display-name" is returned by /cloud/user endpoint - // change to kUserProfileDisplayName ("displayName") when using /cloud/users/{userId} endpoint - managedActiveAccount.userDisplayName = [userProfile objectForKey:@"display-name"]; - managedActiveAccount.userDisplayNameScope = [userProfile objectForKey:kUserProfileDisplayNameScope]; - managedActiveAccount.phone = [userProfile objectForKey:kUserProfilePhone]; - managedActiveAccount.phoneScope = [userProfile objectForKey:kUserProfilePhoneScope]; - managedActiveAccount.email = email; - managedActiveAccount.emailScope = [userProfile objectForKey:kUserProfileEmailScope]; - managedActiveAccount.address = [userProfile objectForKey:kUserProfileAddress]; - managedActiveAccount.addressScope = [userProfile objectForKey:kUserProfileAddressScope]; - managedActiveAccount.website = [userProfile objectForKey:kUserProfileWebsite]; - managedActiveAccount.websiteScope = [userProfile objectForKey:kUserProfileWebsiteScope]; - managedActiveAccount.twitter = [userProfile objectForKey:kUserProfileTwitter]; - managedActiveAccount.twitterScope = [userProfile objectForKey:kUserProfileTwitterScope]; - managedActiveAccount.avatarScope = [userProfile objectForKey:kUserProfileAvatarScope]; + if (!managedActiveAccount) { + NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; + block(error); - [realm commitWriteTransaction]; + return; + } - TalkAccount *unmanagedUpdatedAccount = [[TalkAccount alloc] initWithValue:managedActiveAccount]; - [[NCAPIController sharedInstance] saveProfileImageForAccount:unmanagedUpdatedAccount]; + managedActiveAccount.userId = [userProfile objectForKey:kUserProfileUserId]; + // "display-name" is returned by /cloud/user endpoint + // change to kUserProfileDisplayName ("displayName") when using /cloud/users/{userId} endpoint + managedActiveAccount.userDisplayName = [userProfile objectForKey:@"display-name"]; + managedActiveAccount.userDisplayNameScope = [userProfile objectForKey:kUserProfileDisplayNameScope]; + managedActiveAccount.phone = [userProfile objectForKey:kUserProfilePhone]; + managedActiveAccount.phoneScope = [userProfile objectForKey:kUserProfilePhoneScope]; + managedActiveAccount.email = email; + managedActiveAccount.emailScope = [userProfile objectForKey:kUserProfileEmailScope]; + managedActiveAccount.address = [userProfile objectForKey:kUserProfileAddress]; + managedActiveAccount.addressScope = [userProfile objectForKey:kUserProfileAddressScope]; + managedActiveAccount.website = [userProfile objectForKey:kUserProfileWebsite]; + managedActiveAccount.websiteScope = [userProfile objectForKey:kUserProfileWebsiteScope]; + managedActiveAccount.twitter = [userProfile objectForKey:kUserProfileTwitter]; + managedActiveAccount.twitterScope = [userProfile objectForKey:kUserProfileTwitterScope]; + managedActiveAccount.avatarScope = [userProfile objectForKey:kUserProfileAvatarScope]; + + TalkAccount *unmanagedUpdatedAccount = [[TalkAccount alloc] initWithValue:managedActiveAccount]; + [[NCAPIController sharedInstance] saveProfileImageForAccount:unmanagedUpdatedAccount]; - block(nil); + block(nil); + }]; } else { NSLog(@"Error while getting the user profile"); block(error); @@ -427,18 +425,16 @@ - (void)getUserGroupsAndTeamsForAccountId:(NSString *)accountId [[NCAPIController sharedInstance] getUserGroupsForAccount:account completionBlock:^(NSArray * _Nullable groupIds, NSError * _Nullable error) { if (!error) { RLMRealm *realm = [RLMRealm defaultRealm]; - [realm beginWriteTransaction]; - - NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; - TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; + [realm transactionWithBlock:^{ + NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; + TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; - if (!managedActiveAccount) { - return; - } - - managedActiveAccount.groupIds = groupIds; + if (!managedActiveAccount) { + return; + } - [realm commitWriteTransaction]; + managedActiveAccount.groupIds = groupIds; + }]; } else { NSLog(@"Error while getting user's groups"); } @@ -447,18 +443,16 @@ - (void)getUserGroupsAndTeamsForAccountId:(NSString *)accountId [[NCAPIController sharedInstance] getUserTeamsForAccount:account completionBlock:^(NSArray * _Nullable teamIds, NSError * _Nullable error) { if (!error) { RLMRealm *realm = [RLMRealm defaultRealm]; - [realm beginWriteTransaction]; + [realm transactionWithBlock:^{ + NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; + TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; - NSPredicate *query = [NSPredicate predicateWithFormat:@"accountId = %@", account.accountId]; - TalkAccount *managedActiveAccount = [TalkAccount objectsWithPredicate:query].firstObject; - - if (!managedActiveAccount) { - return; - } - - managedActiveAccount.teamIds = teamIds; + if (!managedActiveAccount) { + return; + } - [realm commitWriteTransaction]; + managedActiveAccount.teamIds = teamIds; + }]; } else { NSLog(@"Error while getting user' teams"); }