From e42a4d80adf434304dc8fb43cc2c9d863d34a692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 20 Sep 2024 11:44:12 +0200 Subject: [PATCH 1/5] Add support for archived conversations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- .pyspelling.wordlist.txt | 3 + NextcloudTalk/NCAPIControllerExtensions.swift | 34 +++ NextcloudTalk/NCDatabaseManager.h | 1 + NextcloudTalk/NCDatabaseManager.m | 3 +- NextcloudTalk/NCRoom.h | 1 + NextcloudTalk/NCRoom.m | 4 +- NextcloudTalk/RoomInfoTableViewController.m | 233 +++++++++++++++--- NextcloudTalk/RoomsTableViewController.m | 58 ++++- NextcloudTalk/en.lproj/Localizable.strings | 21 ++ 9 files changed, 312 insertions(+), 46 deletions(-) diff --git a/.pyspelling.wordlist.txt b/.pyspelling.wordlist.txt index 0fef60d54..e71a69a01 100644 --- a/.pyspelling.wordlist.txt +++ b/.pyspelling.wordlist.txt @@ -18,3 +18,6 @@ Unban unban Zammad Strikethrough +Unarchive +unarchive +unarchived diff --git a/NextcloudTalk/NCAPIControllerExtensions.swift b/NextcloudTalk/NCAPIControllerExtensions.swift index d945a487f..ec8a4c5a0 100644 --- a/NextcloudTalk/NCAPIControllerExtensions.swift +++ b/NextcloudTalk/NCAPIControllerExtensions.swift @@ -525,4 +525,38 @@ import Foundation completionBlock(UserAbsence(dictionary: dataDict)) } } + + // MARK: - Archived conversations + + public func archiveRoom(_ token: String, forAccount account: TalkAccount, completionBlock: @escaping (_ success: Bool) -> Void) { + guard let encodedToken = token.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager + else { + completionBlock(false) + return + } + + let apiVersion = self.conversationAPIVersion(for: account) + let urlString = self.getRequestURL(forEndpoint: "room/\(encodedToken)/archive", withAPIVersion: apiVersion, for: account) + + apiSessionManager.postOcs(urlString, account: account) { _, error in + completionBlock(error == nil) + } + } + + public func unarchiveRoom(_ token: String, forAccount account: TalkAccount, completionBlock: @escaping (_ success: Bool) -> Void) { + guard let encodedToken = token.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let apiSessionManager = self.apiSessionManagers.object(forKey: account.accountId) as? NCAPISessionManager + else { + completionBlock(false) + return + } + + let apiVersion = self.conversationAPIVersion(for: account) + let urlString = self.getRequestURL(forEndpoint: "room/\(encodedToken)/archive", withAPIVersion: apiVersion, for: account) + + apiSessionManager.deleteOcs(urlString, account: account) { _, error in + completionBlock(error == nil) + } + } } diff --git a/NextcloudTalk/NCDatabaseManager.h b/NextcloudTalk/NCDatabaseManager.h index 151f11d87..dba53ac6e 100644 --- a/NextcloudTalk/NCDatabaseManager.h +++ b/NextcloudTalk/NCDatabaseManager.h @@ -75,6 +75,7 @@ extern NSString * const kCapabilityBanV1; extern NSString * const kCapabilityMentionPermissions; extern NSString * const kCapabilityEditMessagesNoteToSelf; extern NSString * const kCapabilityChatSummary; +extern NSString * const kCapabilityArchivedConversations; extern NSString * const kNotificationsCapabilityExists; diff --git a/NextcloudTalk/NCDatabaseManager.m b/NextcloudTalk/NCDatabaseManager.m index ae1c0791d..c7f054463 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 = 70; +uint64_t const kTalkDatabaseSchemaVersion = 71; NSString * const kCapabilitySystemMessages = @"system-messages"; NSString * const kCapabilityNotificationLevels = @"notification-levels"; @@ -76,6 +76,7 @@ NSString * const kCapabilityMentionPermissions = @"mention-permissions"; NSString * const kCapabilityEditMessagesNoteToSelf = @"edit-messages-note-to-self"; NSString * const kCapabilityChatSummary = @"chat-summary-api"; +NSString * const kCapabilityArchivedConversations = @"archived-conversations"; NSString * const kNotificationsCapabilityExists = @"exists"; diff --git a/NextcloudTalk/NCRoom.h b/NextcloudTalk/NCRoom.h index a21f11dda..844f42ebe 100644 --- a/NextcloudTalk/NCRoom.h +++ b/NextcloudTalk/NCRoom.h @@ -142,6 +142,7 @@ extern NSString * const NCRoomObjectTypeRoom; @property (nonatomic, copy) NSString *remoteToken; @property (nonatomic, copy) NSString *lastReceivedProxyHash; @property (nonatomic, assign) NSInteger mentionPermissions; +@property (nonatomic, assign) BOOL isArchived; + (instancetype _Nullable)roomWithDictionary:(NSDictionary * _Nullable)roomDict andAccountId:(NSString * _Nullable)accountId; + (void)updateRoom:(NCRoom * _Nonnull)managedRoom withRoom:(NCRoom * _Nonnull)room; diff --git a/NextcloudTalk/NCRoom.m b/NextcloudTalk/NCRoom.m index ae60fff80..4f4cfba1e 100644 --- a/NextcloudTalk/NCRoom.m +++ b/NextcloudTalk/NCRoom.m @@ -64,6 +64,7 @@ + (instancetype)roomWithDictionary:(NSDictionary *)roomDict andAccountId:(NSStri room.remoteServer = [roomDict objectForKey:@"remoteServer"]; room.remoteToken = [roomDict objectForKey:@"remoteToken"]; room.mentionPermissions = [[roomDict objectForKey:@"mentionPermissions"] integerValue]; + room.isArchived = [[roomDict objectForKey:@"isArchived"] boolValue]; // Local-only field -> update only if there's actually a value if ([roomDict objectForKey:@"pendingMessage"] != nil) { @@ -83,7 +84,7 @@ + (instancetype)roomWithDictionary:(NSDictionary *)roomDict andAccountId:(NSStri } else { room.displayName = [displayName stringValue]; } - + id participants = [roomDict objectForKey:@"participants"]; if ([participants isKindOfClass:[NSDictionary class]]) { room.participants = (RLMArray *)[participants allKeys]; @@ -183,6 +184,7 @@ + (void)updateRoom:(NCRoom *)managedRoom withRoom:(NCRoom *)room managedRoom.remoteToken = room.remoteToken; managedRoom.remoteServer = room.remoteServer; managedRoom.mentionPermissions = room.mentionPermissions; + managedRoom.isArchived = room.isArchived; } + (NSString *)primaryKey { diff --git a/NextcloudTalk/RoomInfoTableViewController.m b/NextcloudTalk/RoomInfoTableViewController.m index f6a90607d..00953884b 100644 --- a/NextcloudTalk/RoomInfoTableViewController.m +++ b/NextcloudTalk/RoomInfoTableViewController.m @@ -40,6 +40,7 @@ kRoomInfoSectionWebinar, kRoomInfoSectionSIP, kRoomInfoSectionParticipants, + kRoomInfoSectionNonDestructive, kRoomInfoSectionDestructive } RoomInfoSection; @@ -78,6 +79,11 @@ kSIPActionNumber } SIPAction; +typedef enum NondestructiveAction { + kNondestructiveActionArchive = 0, + kNondestructiveActionUnarchive +} NondestructiveAction; + typedef enum DestructiveAction { kDestructiveActionLeave = 0, kDestructiveActionClearHistory, @@ -105,6 +111,8 @@ kModificationErrorRoomDescription, kModificationErrorBanActor, kModificationErrorMentionPermissions, + kModificationErrorArchive, + kModificationErrorUnarchive, } ModificationError; typedef enum FileAction { @@ -161,7 +169,7 @@ - (instancetype)initForRoom:(NCRoom *)room fromChatViewController:(ChatViewContr - (void)viewDidLoad { [super viewDidLoad]; - + self.navigationItem.title = NSLocalizedString(@"Conversation settings", nil); [self.navigationController.navigationBar setTitleTextAttributes: @{NSForegroundColorAttributeName:[NCAppBranding themeTextColor]}]; @@ -177,15 +185,15 @@ - (void)viewDidLoad self.navigationItem.standardAppearance = appearance; self.navigationItem.compactAppearance = appearance; self.navigationItem.scrollEdgeAppearance = appearance; - + _roomParticipants = [[NSMutableArray alloc] init]; - + _publicSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_publicSwitch addTarget: self action: @selector(publicValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _listableSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_listableSwitch addTarget: self action: @selector(listableValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _listableForEveryoneSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_listableForEveryoneSwitch addTarget: self action: @selector(listableForEveryoneValueChanged:) forControlEvents:UIControlEventValueChanged]; @@ -194,23 +202,23 @@ - (void)viewDidLoad _readOnlySwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_readOnlySwitch addTarget: self action: @selector(readOnlyValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _lobbySwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_lobbySwitch addTarget: self action: @selector(lobbyValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _sipSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_sipSwitch addTarget: self action: @selector(sipValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _sipNoPINSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_sipNoPINSwitch addTarget: self action: @selector(sipNoPINValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _callNotificationSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; [_callNotificationSwitch addTarget: self action: @selector(callNotificationValueChanged:) forControlEvents:UIControlEventValueChanged]; - + _lobbyDatePicker = [[UIDatePicker alloc] init]; _lobbyDatePicker.datePickerMode = UIDatePickerModeDateAndTime; _lobbyDatePicker.preferredDatePickerStyle = UIDatePickerStyleWheels; - + _lobbyDateTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 00, 150, 30)]; _lobbyDateTextField.textAlignment = NSTextAlignmentRight; _lobbyDateTextField.placeholder = NSLocalizedString(@"Manual", @"TRANSLATORS this is used when no meeting start time is set and the meeting will be started manually"); @@ -218,14 +226,14 @@ - (void)viewDidLoad _lobbyDateTextField.minimumFontSize = 9; [_lobbyDateTextField setInputView:_lobbyDatePicker]; [self setupLobbyDatePicker]; - + _modifyingRoomView = [[UIActivityIndicatorView alloc] init]; _modifyingRoomView.color = [NCAppBranding themeTextColor]; - + _headerView = [[HeaderWithButton alloc] init]; [_headerView.button setTitle:NSLocalizedString(@"Add", nil) forState:UIControlStateNormal]; [_headerView.button addTarget:self action:@selector(addParticipantsButtonPressed) forControlEvents:UIControlEventTouchUpInside]; - + [self.tableView registerNib:[UINib nibWithNibName:kContactsTableCellNibName bundle:nil] forCellReuseIdentifier:kContactCellIdentifier]; [self.tableView registerNib:[UINib nibWithNibName:RoomNameTableViewCell.nibName bundle:nil] forCellReuseIdentifier:RoomNameTableViewCell.identifier]; [self.tableView registerClass:TextViewTableViewCell.class forCellReuseIdentifier:TextViewTableViewCell.identifier]; @@ -235,14 +243,14 @@ - (void)viewDidLoad target:self action:@selector(cancelButtonPressed)]; self.navigationController.navigationBar.topItem.leftBarButtonItem = cancelButton; } - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRoom:) name:NCRoomsManagerDidUpdateRoomNotification object:nil]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - + [[NCRoomsManager sharedInstance] updateRoom:_room.token withCompletionBlock:nil]; [self getRoomParticipants]; } @@ -279,27 +287,33 @@ - (NSArray *)getRoomInfoSections NSMutableArray *sections = [[NSMutableArray alloc] init]; // Room name section [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionName]]; + // Room description section if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityRoomDescription] && _room.roomDescription && ![_room.roomDescription isEqualToString:@""]) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionDescription]]; } + // File actions section if ([_room.objectType isEqualToString:NCRoomObjectTypeFile]) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionFile]]; } + // Shared items section if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityRichObjectListMedia] && ![self.room isFederated]) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionSharedItems]]; } + // Notifications section if ([self getNotificationsActions].count > 0) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionNotifications]]; } + // Conversation section if ([self getConversationActions].count > 0) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionConversation]]; } + // Moderator sections if (_room.canModerate) { // Guests section @@ -309,16 +323,24 @@ - (NSArray *)getRoomInfoSections [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionWebinar]]; } } + // SIP section if (_room.sipState > NCRoomSIPStateDisabled) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionSIP]]; } + // Participants section [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionParticipants]]; + + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionNonDestructive]]; + } + // Destructive actions section if (!_hideDestructiveActions) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionDestructive]]; } + return [NSArray arrayWithArray:sections]; } @@ -344,13 +366,13 @@ - (NSArray *)getNotificationsActions [actions addObject:[NSNumber numberWithInt:kNotificationActionChatNotifications]]; } // Call notifications action - if ([[NCDatabaseManager sharedInstance] roomHasTalkCapability:kCapabilityNotificationCalls forRoom:self.room] && + if ([[NCDatabaseManager sharedInstance] roomHasTalkCapability:kCapabilityNotificationCalls forRoom:self.room] && [[NCDatabaseManager sharedInstance] roomTalkCapabilitiesForRoom:self.room].callEnabled && ![self.room isFederated]) { - + [actions addObject:[NSNumber numberWithInt:kNotificationActionCallNotifications]]; } - + return [NSArray arrayWithArray:actions]; } @@ -372,7 +394,7 @@ - (NSArray *)getFileActions [actions addObject:[NSNumber numberWithInt:kFileActionPreview]]; // Open file in nextcloud app [actions addObject:[NSNumber numberWithInt:kFileActionOpenInFilesApp]]; - + return [NSArray arrayWithArray:actions]; } @@ -422,7 +444,7 @@ - (NSArray *)getConversationActions // Listable room action if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityListableRooms]) { [actions addObject:[NSNumber numberWithInt:kConversationActionListable]]; - + if (_room.listable != NCRoomListableScopeParticipantsOnly && [[NCSettingsController sharedInstance] isGuestsAppEnabled]) { [actions addObject:[NSNumber numberWithInt:kConversationActionListableForEveryone]]; } @@ -442,7 +464,7 @@ - (NSArray *)getConversationActions if (_room.type != kNCRoomTypeChangelog && _room.type != kNCRoomTypeNoteToSelf) { [actions addObject:[NSNumber numberWithInt:kConversationActionShareLink]]; } - + return [NSArray arrayWithArray:actions]; } @@ -476,6 +498,21 @@ - (NSArray *)getWebinarActions return [NSArray arrayWithArray:actions]; } +- (NSArray *)getRoomNondestructiveActions +{ + NSMutableArray *actions = [[NSMutableArray alloc] init]; + + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + if (_room.isArchived) { + [actions addObject:[NSNumber numberWithInt:kNondestructiveActionUnarchive]]; + } else { + [actions addObject:[NSNumber numberWithInt:kNondestructiveActionArchive]]; + } + } + + return actions; +} + - (NSArray *)getRoomDestructiveActions { NSMutableArray *actions = [[NSMutableArray alloc] init]; @@ -542,67 +579,67 @@ - (void)showRoomModificationError:(ModificationError)error withMessage:(NSString case kModificationErrorChatNotifications: errorDescription = NSLocalizedString(@"Could not change notifications setting", nil); break; - + case kModificationErrorCallNotifications: errorDescription = NSLocalizedString(@"Could not change call notifications setting", nil); break; - + case kModificationErrorShare: errorDescription = NSLocalizedString(@"Could not change sharing permissions of the conversation", nil); break; - + case kModificationErrorPassword: errorDescription = NSLocalizedString(@"Could not change password protection settings", nil); break; - + case kModificationErrorResendInvitations: errorDescription = NSLocalizedString(@"Could not resend email invitations", nil); break; - + case kModificationErrorSendCallNotification: errorDescription = NSLocalizedString(@"Could not send call notification", nil); break; - + case kModificationErrorLobby: errorDescription = NSLocalizedString(@"Could not change lobby state of the conversation", nil); break; - + case kModificationErrorSIP: errorDescription = NSLocalizedString(@"Could not change SIP state of the conversation", nil); break; - + case kModificationErrorModeration: errorDescription = NSLocalizedString(@"Could not change moderation permissions of the participant", nil); break; - + case kModificationErrorRemove: errorDescription = NSLocalizedString(@"Could not remove participant", nil); break; - + case kModificationErrorLeave: errorDescription = NSLocalizedString(@"Could not leave conversation", nil); break; - + case kModificationErrorLeaveModeration: errorDescription = NSLocalizedString(@"You need to promote a new moderator before you can leave this conversation", nil); break; - + case kModificationErrorDelete: errorDescription = NSLocalizedString(@"Could not delete conversation", nil); break; - + case kModificationErrorClearHistory: errorDescription = NSLocalizedString(@"Could not clear chat history", nil); break; - + case kModificationErrorListable: errorDescription = NSLocalizedString(@"Could not change listable scope of the conversation", nil); break; - + case kModificationErrorReadOnly: errorDescription = NSLocalizedString(@"Could not change read-only state of the conversation", nil); break; - + case kModificationErrorMessageExpiration: errorDescription = NSLocalizedString(@"Could not set message expiration time", nil); break; @@ -619,10 +656,18 @@ - (void)showRoomModificationError:(ModificationError)error withMessage:(NSString errorDescription = NSLocalizedString(@"Could not change mention permissions of the conversation", nil); break; + case kModificationErrorArchive: + errorDescription = NSLocalizedString(@"Could not archive conversation", nil); + break; + + case kModificationErrorUnarchive: + errorDescription = NSLocalizedString(@"Could not unarchive conversation", nil); + break; + default: break; } - + UIAlertController *renameDialog = [UIAlertController alertControllerWithTitle:errorDescription message:errorMessage @@ -1098,6 +1143,32 @@ - (void)clearHistory }]; } +- (void)archiveRoom +{ + TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; + + [[NCAPIController sharedInstance] archiveRoom:_room.token forAccount:activeAccount completionBlock:^(BOOL success) { + if (!success) { + [self showRoomModificationError:kModificationErrorArchive]; + } + + [[NCRoomsManager sharedInstance] updateRoom:self->_room.token withCompletionBlock:nil]; + }]; +} + +- (void)unarchiveRoom +{ + TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; + + [[NCAPIController sharedInstance] unarchiveRoom:_room.token forAccount:activeAccount completionBlock:^(BOOL success) { + if (!success) { + [self showRoomModificationError:kModificationErrorUnarchive]; + } + + [[NCRoomsManager sharedInstance] updateRoom:self->_room.token withCompletionBlock:nil]; + }]; +} + - (void)leaveRoom { [[NCAPIController sharedInstance] removeSelfFromRoom:_room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSInteger errorCode, NSError *error) { @@ -1639,7 +1710,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger case kRoomInfoSectionParticipants: return _roomParticipants.count; break; - + + case kRoomInfoSectionNonDestructive: + return [self getRoomNondestructiveActions].count; + break; + case kRoomInfoSectionDestructive: return [self getRoomDestructiveActions].count; break; @@ -1694,6 +1769,26 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte return nil; } +- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section +{ + NSArray *sections = [self getRoomInfoSections]; + RoomInfoSection infoSection = [[sections objectAtIndex:section] intValue]; + switch (infoSection) { + case kRoomInfoSectionNonDestructive: + if (!_room.isArchived) { + return NSLocalizedString(@"Once a conversation is archived, it will be hidden by default. Select the filter 'Archived' to view archived conversations. Direct mentions will still be received.", nil); + } else { + return NSLocalizedString(@"Once a conversation is unarchived, it will be shown by default again.", nil); + } + + break; + default: + break; + } + + return nil; +} + - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { NSArray *sections = [self getRoomInfoSections]; @@ -1767,7 +1862,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N static NSString *listableForEveryoneCellIdentifier = @"ListableForEveryoneCellIdentifier"; static NSString *mentionPermissionsCellIdentifier = @"mentionPermissionsCellIdentifier"; static NSString *readOnlyStateCellIdentifier = @"ReadOnlyStateCellIdentifier"; - + static NSString *archiveConversationCellIdentifier = @"ArchiveConversationCellIdentifier"; + static NSString *unarchiveConversationCellIdentifier = @"UnarchiveConversationCellIdentifier"; + NSArray *sections = [self getRoomInfoSections]; RoomInfoSection section = [[sections objectAtIndex:indexPath.section] intValue]; switch (section) { @@ -2335,6 +2432,44 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } break; + case kRoomInfoSectionNonDestructive: + { + NSArray *actions = [self getRoomNondestructiveActions]; + NondestructiveAction action = [[actions objectAtIndex:indexPath.row] intValue]; + switch (action) { + case kNondestructiveActionArchive: + { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:archiveConversationCellIdentifier]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:archiveConversationCellIdentifier]; + } + + cell.textLabel.text = NSLocalizedString(@"Archive conversation", nil); + cell.textLabel.numberOfLines = 0; + UIImage *image = [[UIImage systemImageNamed:@"archivebox"] imageWithTintColor:[UIColor labelColor] renderingMode:UIImageRenderingModeAlwaysOriginal]; + [cell.imageView setImage:image]; + + return cell; + } + break; + case kNondestructiveActionUnarchive: + { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:unarchiveConversationCellIdentifier]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:unarchiveConversationCellIdentifier]; + } + + cell.textLabel.text = NSLocalizedString(@"Unarchive conversation", nil); + cell.textLabel.numberOfLines = 0; + UIImage *image = [[UIImage systemImageNamed:@"eye"] imageWithTintColor:[UIColor labelColor] renderingMode:UIImageRenderingModeAlwaysOriginal]; + [cell.imageView setImage:image]; + + return cell; + } + break; + } + } + break; case kRoomInfoSectionDestructive: { NSArray *actions = [self getRoomDestructiveActions]; @@ -2484,6 +2619,22 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self showOptionsForParticipantAtIndexPath:indexPath]; } break; + case kRoomInfoSectionNonDestructive: + { + NSArray *actions = [self getRoomNondestructiveActions]; + NondestructiveAction action = [[actions objectAtIndex:indexPath.row] intValue]; + switch (action) { + case kNondestructiveActionArchive: + [self archiveRoom]; + break; + case kNondestructiveActionUnarchive: + [self unarchiveRoom]; + break; + default: + break; + } + } + break; case kRoomInfoSectionDestructive: { NSArray *actions = [self getRoomDestructiveActions]; diff --git a/NextcloudTalk/RoomsTableViewController.m b/NextcloudTalk/RoomsTableViewController.m index b832bc48d..67a7eb465 100644 --- a/NextcloudTalk/RoomsTableViewController.m +++ b/NextcloudTalk/RoomsTableViewController.m @@ -33,7 +33,8 @@ typedef enum RoomsFilter { kRoomsFilterAll = 0, kRoomsFilterUnread, - kRoomsFilterMentioned + kRoomsFilterMentioned, + kRoomsFilterArchived } RoomsFilter; typedef enum RoomsSections { @@ -708,9 +709,11 @@ - (NSArray *)filterRoomsWithFilter:(RoomsFilter)filter case kRoomsFilterUnread: return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"unreadMessages > 0"]]; case kRoomsFilterMentioned: - return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"hasUnreadMention == YES"]]; + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"hasUnreadMention == YES AND isArchived == NO"]]; + case kRoomsFilterArchived: + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == YES"]]; default: - return _allRooms; + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == NO OR hasUnreadMention == YES"]]; } } @@ -750,6 +753,10 @@ - (NSArray *)availableFilters [filters addObject:[NSNumber numberWithInt:kRoomsFilterUnread]]; [filters addObject:[NSNumber numberWithInt:kRoomsFilterMentioned]]; + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + [filters addObject:[NSNumber numberWithInt:kRoomsFilterArchived]]; + } + return [NSArray arrayWithArray:filters]; } @@ -762,6 +769,8 @@ - (NSString *)filterName:(RoomsFilter)filter return NSLocalizedString(@"Unread", @"'Unread' meaning 'Unread conversations'"); case kRoomsFilterMentioned: return NSLocalizedString(@"Mentioned", @"'Mentioned' meaning 'Mentioned conversations'"); + case kRoomsFilterArchived: + return NSLocalizedString(@"Archived", @"'Archived' meaning 'Archived conversations'"); default: return @""; } @@ -1063,6 +1072,32 @@ - (void)shareLinkFromRoom:(NCRoom *)room } } +- (void)archiveRoom:(NCRoom *)room +{ + TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; + + [[NCAPIController sharedInstance] archiveRoom:room.token forAccount:activeAccount completionBlock:^(BOOL success) { + if (!success) { + NSLog(@"Error archiving room"); + } + + [[NCRoomsManager sharedInstance] updateRoomsUpdatingUserStatus:YES onlyLastModified:NO]; + }]; +} + +- (void)unarchiveRoom:(NCRoom *)room +{ + TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount]; + + [[NCAPIController sharedInstance] unarchiveRoom:room.token forAccount:activeAccount completionBlock:^(BOOL success) { + if (!success) { + NSLog(@"Error unarchiving room"); + } + + [[NCRoomsManager sharedInstance] updateRoomsUpdatingUserStatus:YES onlyLastModified:NO]; + }]; +} + - (void)markRoomAsRead:(NCRoom *)room { [[NCAPIController sharedInstance] setChatReadMarker:room.lastMessage.messageId inRoom:room.token forAccount:[[NCDatabaseManager sharedInstance] activeAccount] withCompletionBlock:^(NSError *error) { @@ -1605,6 +1640,23 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuCo [actions addObject:notificationActions]; } + // Archive conversation + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + if (room.isArchived) { + UIAction *unarchiveAction = [UIAction actionWithTitle:NSLocalizedString(@"Unarchive conversation", nil) image:[UIImage systemImageNamed:@"eye"] identifier:nil handler:^(UIAction *action) { + [weakSelf unarchiveRoom:room]; + }]; + + [actions addObject:unarchiveAction]; + } else { + UIAction *archiveAction = [UIAction actionWithTitle:NSLocalizedString(@"Archive conversation", nil) image:[UIImage systemImageNamed:@"archivebox"] identifier:nil handler:^(UIAction *action) { + [weakSelf archiveRoom:room]; + }]; + + [actions addObject:archiveAction]; + } + } + // Room info UIAction *roomInfoAction = [UIAction actionWithTitle:NSLocalizedString(@"Conversation settings", nil) image:[UIImage systemImageNamed:@"gearshape"] identifier:nil handler:^(UIAction *action) { [weakSelf presentRoomInfoForRoom:room]; diff --git a/NextcloudTalk/en.lproj/Localizable.strings b/NextcloudTalk/en.lproj/Localizable.strings index d30f12684..2570c5b23 100644 --- a/NextcloudTalk/en.lproj/Localizable.strings +++ b/NextcloudTalk/en.lproj/Localizable.strings @@ -307,6 +307,12 @@ /* No comment provided by engineer. */ "Appear offline" = "Appear offline"; +/* No comment provided by engineer. */ +"Archive conversation" = "Archive conversation"; + +/* 'Archived' meaning 'Archived conversations' */ +"Archived" = "Archived"; + /* Alice and Bob are typing… */ "are typing…" = "are typing…"; @@ -514,6 +520,9 @@ /* No comment provided by engineer. */ "Could not add participant" = "Could not add participant"; +/* No comment provided by engineer. */ +"Could not archive conversation" = "Could not archive conversation"; + /* No comment provided by engineer. */ "Could not ban participant" = "Could not ban participant"; @@ -604,6 +613,9 @@ /* No comment provided by engineer. */ "Could not share file" = "Could not share file"; +/* No comment provided by engineer. */ +"Could not unarchive conversation" = "Could not unarchive conversation"; + /* No comment provided by engineer. */ "Create" = "Create"; @@ -1279,9 +1291,15 @@ /* Will be used as the caller name when a VoIP notification can't be decrypted */ "Old account" = "Old account"; +/* No comment provided by engineer. */ +"Once a conversation is archived, it will be hidden by default. Select the filter 'Archived' to view archived conversations. Direct mentions will still be received." = "Once a conversation is archived, it will be hidden by default. Select the filter 'Archived' to view archived conversations. Direct mentions will still be received."; + /* No comment provided by engineer. */ "Once a conversation is left, to rejoin a closed conversation, an invite is needed. An open conversation can be rejoined at any time." = "Once a conversation is left, to rejoin a closed conversation, an invite is needed. An open conversation can be rejoined at any time."; +/* No comment provided by engineer. */ +"Once a conversation is unarchived, it will be shown by default again." = "Once a conversation is unarchived, it will be shown by default again."; + /* No comment provided by engineer. */ "Online" = "Online"; @@ -1804,6 +1822,9 @@ /* No comment provided by engineer. */ "Unable to open file" = "Unable to open file"; +/* No comment provided by engineer. */ +"Unarchive conversation" = "Unarchive conversation"; + /* No comment provided by engineer. */ "Unavailable" = "Unavailable"; From bc2d8b784faca7ff88274ad79cbf3b8c9caf72a8 Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Tue, 26 Nov 2024 17:46:58 +0100 Subject: [PATCH 2/5] Removed "Archived" filter and added "Archived conversations" info cell at the top of conversations list. Signed-off-by: Ivan Sein --- NextcloudTalk/NCDatabaseManager.h | 2 +- NextcloudTalk/NCDatabaseManager.m | 2 +- NextcloudTalk/RoomInfoTableViewController.m | 8 +- NextcloudTalk/RoomsTableViewController.m | 86 ++++++++++++++------- 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/NextcloudTalk/NCDatabaseManager.h b/NextcloudTalk/NCDatabaseManager.h index dba53ac6e..511932de8 100644 --- a/NextcloudTalk/NCDatabaseManager.h +++ b/NextcloudTalk/NCDatabaseManager.h @@ -75,7 +75,7 @@ extern NSString * const kCapabilityBanV1; extern NSString * const kCapabilityMentionPermissions; extern NSString * const kCapabilityEditMessagesNoteToSelf; extern NSString * const kCapabilityChatSummary; -extern NSString * const kCapabilityArchivedConversations; +extern NSString * const kCapabilityArchivedConversationsV2; extern NSString * const kNotificationsCapabilityExists; diff --git a/NextcloudTalk/NCDatabaseManager.m b/NextcloudTalk/NCDatabaseManager.m index c7f054463..8b2640905 100644 --- a/NextcloudTalk/NCDatabaseManager.m +++ b/NextcloudTalk/NCDatabaseManager.m @@ -76,7 +76,7 @@ NSString * const kCapabilityMentionPermissions = @"mention-permissions"; NSString * const kCapabilityEditMessagesNoteToSelf = @"edit-messages-note-to-self"; NSString * const kCapabilityChatSummary = @"chat-summary-api"; -NSString * const kCapabilityArchivedConversations = @"archived-conversations"; +NSString * const kCapabilityArchivedConversationsV2 = @"archived-conversations-v2"; NSString * const kNotificationsCapabilityExists = @"exists"; diff --git a/NextcloudTalk/RoomInfoTableViewController.m b/NextcloudTalk/RoomInfoTableViewController.m index 00953884b..8e89ceaf2 100644 --- a/NextcloudTalk/RoomInfoTableViewController.m +++ b/NextcloudTalk/RoomInfoTableViewController.m @@ -332,7 +332,7 @@ - (NSArray *)getRoomInfoSections // Participants section [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionParticipants]]; - if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversationsV2]) { [sections addObject:[NSNumber numberWithInt:kRoomInfoSectionNonDestructive]]; } @@ -502,7 +502,7 @@ - (NSArray *)getRoomNondestructiveActions { NSMutableArray *actions = [[NSMutableArray alloc] init]; - if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversationsV2]) { if (_room.isArchived) { [actions addObject:[NSNumber numberWithInt:kNondestructiveActionUnarchive]]; } else { @@ -1776,7 +1776,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte switch (infoSection) { case kRoomInfoSectionNonDestructive: if (!_room.isArchived) { - return NSLocalizedString(@"Once a conversation is archived, it will be hidden by default. Select the filter 'Archived' to view archived conversations. Direct mentions will still be received.", nil); + return NSLocalizedString(@"Archived conversations are hidden from the conversation list by default. They will only be shown when you open archived conversations list.", nil); } else { return NSLocalizedString(@"Once a conversation is unarchived, it will be shown by default again.", nil); } @@ -2461,7 +2461,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.textLabel.text = NSLocalizedString(@"Unarchive conversation", nil); cell.textLabel.numberOfLines = 0; - UIImage *image = [[UIImage systemImageNamed:@"eye"] imageWithTintColor:[UIColor labelColor] renderingMode:UIImageRenderingModeAlwaysOriginal]; + UIImage *image = [[UIImage systemImageNamed:@"arrow.up.bin"] imageWithTintColor:[UIColor labelColor] renderingMode:UIImageRenderingModeAlwaysOriginal]; [cell.imageView setImage:image]; return cell; diff --git a/NextcloudTalk/RoomsTableViewController.m b/NextcloudTalk/RoomsTableViewController.m index 67a7eb465..0309f6ea2 100644 --- a/NextcloudTalk/RoomsTableViewController.m +++ b/NextcloudTalk/RoomsTableViewController.m @@ -33,12 +33,12 @@ typedef enum RoomsFilter { kRoomsFilterAll = 0, kRoomsFilterUnread, - kRoomsFilterMentioned, - kRoomsFilterArchived + kRoomsFilterMentioned } RoomsFilter; typedef enum RoomsSections { kRoomsSectionPendingFederationInvitation = 0, + kRoomsSectionArchivedConversations, kRoomsSectionRoomList } RoomsSections; @@ -47,6 +47,7 @@ @interface RoomsTableViewController () 0"]]; + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"unreadMessages > 0 AND isArchived == %@", @(_showingArchivedRooms)]]; case kRoomsFilterMentioned: - return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"hasUnreadMention == YES AND isArchived == NO"]]; - case kRoomsFilterArchived: - return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == YES"]]; + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"hasUnreadMention == YES AND isArchived == %@", @(_showingArchivedRooms)]]; default: - return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == NO OR hasUnreadMention == YES"]]; + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == %@", @(_showingArchivedRooms)]]; } } @@ -753,10 +752,6 @@ - (NSArray *)availableFilters [filters addObject:[NSNumber numberWithInt:kRoomsFilterUnread]]; [filters addObject:[NSNumber numberWithInt:kRoomsFilterMentioned]]; - if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { - [filters addObject:[NSNumber numberWithInt:kRoomsFilterArchived]]; - } - return [NSArray arrayWithArray:filters]; } @@ -769,8 +764,6 @@ - (NSString *)filterName:(RoomsFilter)filter return NSLocalizedString(@"Unread", @"'Unread' meaning 'Unread conversations'"); case kRoomsFilterMentioned: return NSLocalizedString(@"Mentioned", @"'Mentioned' meaning 'Mentioned conversations'"); - case kRoomsFilterArchived: - return NSLocalizedString(@"Archived", @"'Archived' meaning 'Archived conversations'"); default: return @""; } @@ -1326,19 +1319,19 @@ - (void)createRoomForSelectedUser:(NCUser *)user - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 2; + return 3; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (section == kRoomsSectionPendingFederationInvitation) { TalkAccount *account = [[NCDatabaseManager sharedInstance] activeAccount]; + return account.pendingFederationInvitations > 0 ? 1 : 0; + } - if (account.pendingFederationInvitations > 0) { - return 1; - } - - return 0; + if (section == kRoomsSectionArchivedConversations) { + NSArray *archivedRooms = [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == YES"]]; + return archivedRooms.count > 0 || _showingArchivedRooms ? 1 : 0; } return _rooms.count; @@ -1346,8 +1339,9 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { - if (tableView == self.tableView && indexPath.section == kRoomsSectionPendingFederationInvitation) { - // No swipe action for pending invitations + if (tableView == self.tableView && + (indexPath.section == kRoomsSectionPendingFederationInvitation || indexPath.section == kRoomsSectionArchivedConversations)) { + // No swipe action for pending invitations or archived conversations return nil; } @@ -1379,8 +1373,9 @@ - (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwip - (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - if (tableView == self.tableView && indexPath.section == kRoomsSectionPendingFederationInvitation) { - // No swipe action for pending invitations + if (tableView == self.tableView && + (indexPath.section == kRoomsSectionPendingFederationInvitation || indexPath.section == kRoomsSectionArchivedConversations)) { + // No swipe action for pending invitations or archived conversations return nil; } @@ -1461,6 +1456,33 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } + if (indexPath.section == kRoomsSectionArchivedConversations) { + InfoLabelTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:InfoLabelTableViewCell.identifier]; + if (!cell) { + cell = [[InfoLabelTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:InfoLabelTableViewCell.identifier]; + } + + NSString *actionString = _showingArchivedRooms ? NSLocalizedString(@"Back to conversations", nil) : NSLocalizedString(@"Archived conversations", nil); + NSString *iconName = _showingArchivedRooms ? @"arrow.left" : @"archivebox"; + UIFont *resultFont = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; + + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + attachment.image = [[UIImage systemImageNamed:iconName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + attachment.bounds = CGRectMake(0, roundf(resultFont.capHeight - 20) / 2, 24, 20); + + NSMutableAttributedString *resultString = [[NSMutableAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; + [resultString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; + [resultString appendAttributedString:[[NSAttributedString alloc] initWithString:actionString]]; + + NSRange range = NSMakeRange(0, [resultString length]); + [resultString addAttribute:NSFontAttributeName value:[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline] range:range]; + + cell.label.attributedText = resultString; + cell.separatorInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, CGFLOAT_MAX); + + return cell; + } + RoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:RoomTableViewCell.identifier]; if (!cell) { cell = [[RoomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RoomTableViewCell.identifier]; @@ -1509,7 +1531,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)rcell forRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == kRoomsSectionPendingFederationInvitation || tableView != self.tableView) { + if (tableView != self.tableView || + indexPath.section == kRoomsSectionPendingFederationInvitation || + indexPath.section == kRoomsSectionArchivedConversations) { return; } @@ -1541,6 +1565,12 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath return; } + if (tableView == self.tableView && indexPath.section == kRoomsSectionArchivedConversations) { + _showingArchivedRooms = !_showingArchivedRooms; + [self filterRooms]; + return; + } + if (tableView == _resultTableViewController.tableView) { // Messages NKSearchEntry *message = [_resultTableViewController messageForIndexPath:indexPath]; @@ -1563,7 +1593,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point { - if (![tableView isEqual:self.tableView]) { + if (tableView != self.tableView || + indexPath.section == kRoomsSectionPendingFederationInvitation || + indexPath.section == kRoomsSectionArchivedConversations) { return nil; } @@ -1641,9 +1673,9 @@ - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuCo } // Archive conversation - if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversations]) { + if ([[NCDatabaseManager sharedInstance] serverHasTalkCapability:kCapabilityArchivedConversationsV2]) { if (room.isArchived) { - UIAction *unarchiveAction = [UIAction actionWithTitle:NSLocalizedString(@"Unarchive conversation", nil) image:[UIImage systemImageNamed:@"eye"] identifier:nil handler:^(UIAction *action) { + UIAction *unarchiveAction = [UIAction actionWithTitle:NSLocalizedString(@"Unarchive conversation", nil) image:[UIImage systemImageNamed:@"arrow.up.bin"] identifier:nil handler:^(UIAction *action) { [weakSelf unarchiveRoom:room]; }]; From c54c01ddcfd155b0dc587b64523642bf3bc12d39 Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Tue, 26 Nov 2024 18:28:35 +0100 Subject: [PATCH 3/5] Show unread mentions indicator for archived conversations. Signed-off-by: Ivan Sein --- NextcloudTalk/RoomsTableViewController.m | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/NextcloudTalk/RoomsTableViewController.m b/NextcloudTalk/RoomsTableViewController.m index 0309f6ea2..39443ca30 100644 --- a/NextcloudTalk/RoomsTableViewController.m +++ b/NextcloudTalk/RoomsTableViewController.m @@ -1232,6 +1232,16 @@ - (NSIndexPath *)indexPathForRoom:(NCRoom *)room return nil; } +- (NSArray *)archivedRooms +{ + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == YES"]]; +} + +- (BOOL)areArchivedRoomsWithUnreadMentions +{ + return [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"hasUnreadMention == YES AND isArchived == YES"]].count > 0; +} + - (void)showLeaveRoomLastModeratorErrorForRoom:(NCRoom *)room { UIAlertController *leaveRoomFailedDialog = @@ -1330,8 +1340,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } if (section == kRoomsSectionArchivedConversations) { - NSArray *archivedRooms = [_allRooms filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isArchived == YES"]]; - return archivedRooms.count > 0 || _showingArchivedRooms ? 1 : 0; + return [self archivedRooms].count > 0 || _showingArchivedRooms ? 1 : 0; } return _rooms.count; @@ -1477,6 +1486,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N NSRange range = NSMakeRange(0, [resultString length]); [resultString addAttribute:NSFontAttributeName value:[UIFont preferredFontForTextStyle:UIFontTextStyleHeadline] range:range]; + if (!_showingArchivedRooms && [self areArchivedRoomsWithUnreadMentions]) { + NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; + attachment.image = [[UIImage systemImageNamed:@"circle.fill"] imageWithTintColor:[NCAppBranding elementColor] renderingMode:UIImageRenderingModeAlwaysTemplate]; + attachment.bounds = CGRectMake(0, roundf(resultFont.capHeight - 20) / 2, 20, 20); + + [resultString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; + [resultString appendAttributedString:[[NSAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]]; + } + cell.label.attributedText = resultString; cell.separatorInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, CGFLOAT_MAX); From 3c7f42541ae9b7921d66f87155659b2b9d0dc38f Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Tue, 26 Nov 2024 18:55:23 +0100 Subject: [PATCH 4/5] Use correct section for room list section indexes. Signed-off-by: Ivan Sein --- NextcloudTalk/RoomsTableViewController.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NextcloudTalk/RoomsTableViewController.m b/NextcloudTalk/RoomsTableViewController.m index 39443ca30..1a5a6f568 100644 --- a/NextcloudTalk/RoomsTableViewController.m +++ b/NextcloudTalk/RoomsTableViewController.m @@ -916,7 +916,7 @@ - (void)updateMentionsIndicator for (int i = (int)lastVisibleRowIndexPath.row; i <= (int)_lastRoomWithMentionIndexPath.row && i < [_rooms count]; i++) { NCRoom *room = [_rooms objectAtIndex:i]; if (room.hasUnreadMention) { - _nextRoomWithMentionIndexPath = [NSIndexPath indexPathForRow:i inSection:1]; + _nextRoomWithMentionIndexPath = [NSIndexPath indexPathForRow:i inSection:kRoomsSectionRoomList]; break; } } @@ -942,7 +942,7 @@ - (void)calculateLastRoomWithMention for (int i = 0; i < _rooms.count; i++) { NCRoom *room = [_rooms objectAtIndex:i]; if (room.hasUnreadMention) { - _lastRoomWithMentionIndexPath = [NSIndexPath indexPathForRow:i inSection:1]; + _lastRoomWithMentionIndexPath = [NSIndexPath indexPathForRow:i inSection:kRoomsSectionRoomList]; } } } @@ -1226,7 +1226,7 @@ - (NSIndexPath *)indexPathForRoom:(NCRoom *)room }]; if (idx != NSNotFound) { - return [NSIndexPath indexPathForRow:idx inSection:1]; + return [NSIndexPath indexPathForRow:idx inSection:kRoomsSectionRoomList]; } return nil; @@ -1808,7 +1808,7 @@ - (void)highlightSelectedRoom }]; if (idx != NSNotFound) { - NSIndexPath* indexPath = [NSIndexPath indexPathForRow:idx inSection:1]; + NSIndexPath* indexPath = [NSIndexPath indexPathForRow:idx inSection:kRoomsSectionRoomList]; [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; } } else { From 695674a69929424092f835ce95198c9d41b5f127 Mon Sep 17 00:00:00 2001 From: Ivan Sein Date: Thu, 28 Nov 2024 14:10:43 +0100 Subject: [PATCH 5/5] Update localizable strings file. Signed-off-by: Ivan Sein --- NextcloudTalk/en.lproj/Localizable.strings | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/NextcloudTalk/en.lproj/Localizable.strings b/NextcloudTalk/en.lproj/Localizable.strings index 2570c5b23..036791307 100644 --- a/NextcloudTalk/en.lproj/Localizable.strings +++ b/NextcloudTalk/en.lproj/Localizable.strings @@ -310,8 +310,11 @@ /* No comment provided by engineer. */ "Archive conversation" = "Archive conversation"; -/* 'Archived' meaning 'Archived conversations' */ -"Archived" = "Archived"; +/* No comment provided by engineer. */ +"Archived conversations" = "Archived conversations"; + +/* No comment provided by engineer. */ +"Archived conversations are hidden from the conversation list by default. They will only be shown when you open archived conversations list." = "Archived conversations are hidden from the conversation list by default. They will only be shown when you open archived conversations list."; /* Alice and Bob are typing… */ "are typing…" = "are typing…"; @@ -331,6 +334,9 @@ /* No comment provided by engineer. */ "Away" = "Away"; +/* No comment provided by engineer. */ +"Back to conversations" = "Back to conversations"; + /* Ban a user/guest */ "Ban" = "Ban"; @@ -1291,9 +1297,6 @@ /* Will be used as the caller name when a VoIP notification can't be decrypted */ "Old account" = "Old account"; -/* No comment provided by engineer. */ -"Once a conversation is archived, it will be hidden by default. Select the filter 'Archived' to view archived conversations. Direct mentions will still be received." = "Once a conversation is archived, it will be hidden by default. Select the filter 'Archived' to view archived conversations. Direct mentions will still be received."; - /* No comment provided by engineer. */ "Once a conversation is left, to rejoin a closed conversation, an invite is needed. An open conversation can be rejoined at any time." = "Once a conversation is left, to rejoin a closed conversation, an invite is needed. An open conversation can be rejoined at any time.";