From 670ef4263458dfca9eff86b2cbde39e56be00ffa Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Sun, 31 Dec 2017 12:52:22 +0100 Subject: [PATCH] Group details: Add Rooms page https://github.com/vector-im/riot-meta/issues/117 --- Riot.xcodeproj/project.pbxproj | 22 + Riot/Assets/en.lproj/Vector.strings | 6 +- Riot/Assets/fr.lproj/Vector.strings | 5 +- .../GroupDetailsViewController.m | 15 +- .../GroupParticipantsViewController.m | 31 +- .../ViewController/GroupRoomsViewController.h | 73 +++ .../ViewController/GroupRoomsViewController.m | 602 ++++++++++++++++++ .../GroupRoomsViewController.xib | 87 +++ Riot/Views/RoomList/GroupRoomTableViewCell.h | 32 + Riot/Views/RoomList/GroupRoomTableViewCell.m | 92 +++ .../Views/RoomList/GroupRoomTableViewCell.xib | 68 ++ 11 files changed, 1011 insertions(+), 22 deletions(-) create mode 100644 Riot/ViewController/GroupRoomsViewController.h create mode 100644 Riot/ViewController/GroupRoomsViewController.m create mode 100644 Riot/ViewController/GroupRoomsViewController.xib create mode 100644 Riot/Views/RoomList/GroupRoomTableViewCell.h create mode 100644 Riot/Views/RoomList/GroupRoomTableViewCell.m create mode 100644 Riot/Views/RoomList/GroupRoomTableViewCell.xib diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 57644e918..3495408e0 100755 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -589,6 +589,10 @@ F0E05A451EA0F9EB004B83FB /* tab_rooms.png in Resources */ = {isa = PBXBuildFile; fileRef = F0E05A2D1EA0F9EB004B83FB /* tab_rooms.png */; }; F0E05A461EA0F9EB004B83FB /* tab_rooms@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0E05A2E1EA0F9EB004B83FB /* tab_rooms@2x.png */; }; F0E05A471EA0F9EB004B83FB /* tab_rooms@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0E05A2F1EA0F9EB004B83FB /* tab_rooms@3x.png */; }; + F0E5D90E1FF6DDCF00560D7F /* GroupRoomsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0E5D90C1FF6DDCF00560D7F /* GroupRoomsViewController.m */; }; + F0E5D90F1FF6DDCF00560D7F /* GroupRoomsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0E5D90D1FF6DDCF00560D7F /* GroupRoomsViewController.xib */; }; + F0E5D9131FF6FF3F00560D7F /* GroupRoomTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F0E5D9101FF6FF3E00560D7F /* GroupRoomTableViewCell.m */; }; + F0E5D9141FF6FF3F00560D7F /* GroupRoomTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0E5D9111FF6FF3F00560D7F /* GroupRoomTableViewCell.xib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1380,6 +1384,12 @@ F0E05A2D1EA0F9EB004B83FB /* tab_rooms.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tab_rooms.png; sourceTree = ""; }; F0E05A2E1EA0F9EB004B83FB /* tab_rooms@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_rooms@2x.png"; sourceTree = ""; }; F0E05A2F1EA0F9EB004B83FB /* tab_rooms@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_rooms@3x.png"; sourceTree = ""; }; + F0E5D90B1FF6DDCF00560D7F /* GroupRoomsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupRoomsViewController.h; sourceTree = ""; }; + F0E5D90C1FF6DDCF00560D7F /* GroupRoomsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GroupRoomsViewController.m; sourceTree = ""; }; + F0E5D90D1FF6DDCF00560D7F /* GroupRoomsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GroupRoomsViewController.xib; sourceTree = ""; }; + F0E5D9101FF6FF3E00560D7F /* GroupRoomTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GroupRoomTableViewCell.m; sourceTree = ""; }; + F0E5D9111FF6FF3F00560D7F /* GroupRoomTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GroupRoomTableViewCell.xib; sourceTree = ""; }; + F0E5D9121FF6FF3F00560D7F /* GroupRoomTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupRoomTableViewCell.h; sourceTree = ""; }; F546BCBBB9BBEE67DB28878A /* Pods-RiotPods-Riot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-Riot.release.xcconfig"; path = "Pods/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot.release.xcconfig"; sourceTree = ""; }; FD9D0BDE9232898950554DD5 /* libPods-Riot.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Riot.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -2115,6 +2125,9 @@ F083BC191E7009EC00A9B29C /* ViewController */ = { isa = PBXGroup; children = ( + F0E5D90B1FF6DDCF00560D7F /* GroupRoomsViewController.h */, + F0E5D90C1FF6DDCF00560D7F /* GroupRoomsViewController.m */, + F0E5D90D1FF6DDCF00560D7F /* GroupRoomsViewController.xib */, F046DC7C1FE1A40F00E3DAF0 /* GroupParticipantsViewController.h */, F046DC7B1FE1A40F00E3DAF0 /* GroupParticipantsViewController.m */, F046DC7D1FE1A40F00E3DAF0 /* GroupParticipantsViewController.xib */, @@ -2459,6 +2472,9 @@ F083BCDC1E7009EC00A9B29C /* RoomList */ = { isa = PBXGroup; children = ( + F0E5D9121FF6FF3F00560D7F /* GroupRoomTableViewCell.h */, + F0E5D9101FF6FF3E00560D7F /* GroupRoomTableViewCell.m */, + F0E5D9111FF6FF3F00560D7F /* GroupRoomTableViewCell.xib */, F075BED31EBB169C00A7B68A /* RoomCollectionViewCell.h */, F075BED41EBB169C00A7B68A /* RoomCollectionViewCell.m */, F075BED51EBB169C00A7B68A /* RoomCollectionViewCell.xib */, @@ -3048,6 +3064,7 @@ F083BDDA1E7009ED00A9B29C /* typing.png in Resources */, F083BE831E7009ED00A9B29C /* RecentTableViewCell.xib in Resources */, F046DC731FE1786500E3DAF0 /* GroupHomeViewController.xib in Resources */, + F0E5D9141FF6FF3F00560D7F /* GroupRoomTableViewCell.xib in Resources */, F083BDB71E7009ED00A9B29C /* remove_icon@2x.png in Resources */, F083BDD31E7009ED00A9B29C /* settings_icon@3x.png in Resources */, F04AF26B1F83A4C100D20F4D /* Vector.strings in Resources */, @@ -3207,6 +3224,7 @@ F083BD311E7009ED00A9B29C /* bubbles_bg_landscape@2x.png in Resources */, F0E05A371EA0F9EB004B83FB /* tab_home_selected@2x.png in Resources */, F0D9F23E1FED1A05001D9A27 /* add_group_participant.png in Resources */, + F0E5D90F1FF6DDCF00560D7F /* GroupRoomsViewController.xib in Resources */, F083BE651E7009ED00A9B29C /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, F083BD6F1E7009ED00A9B29C /* e2e_blocked@2x.png in Resources */, ); @@ -3272,6 +3290,7 @@ "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKContactDetailsViewController.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKContactListViewController.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKCountryPickerViewController.xib", + "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKGroupListViewController.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKLanguagePickerViewController.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRecentListViewController.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.xib", @@ -3285,6 +3304,7 @@ "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Contact/MXKContactTableCell.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Views/DeviceView/MXKDeviceView.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.xib", + "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Group/MXKGroupTableViewCell.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKCollectionViewCell/MXKMediaCollectionViewCell.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKEventDetailsView.xib", "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKPieChartHUD.xib", @@ -3444,6 +3464,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F0E5D9131FF6FF3F00560D7F /* GroupRoomTableViewCell.m in Sources */, F083BE221E7009ED00A9B29C /* RoomSettingsViewController.m in Sources */, F083BE171E7009ED00A9B29C /* MediaPickerViewController.m in Sources */, F083BE421E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, @@ -3549,6 +3570,7 @@ 3233F73F1F331F05006ACA81 /* Widget.m in Sources */, F083BE191E7009ED00A9B29C /* RecentsViewController.m in Sources */, F0B4CBA51F418D0B008E99C5 /* WebViewViewController.m in Sources */, + F0E5D90E1FF6DDCF00560D7F /* GroupRoomsViewController.m in Sources */, F083BE351E7009ED00A9B29C /* MediaAlbumTableCell.m in Sources */, F083BE4C1E7009ED00A9B29C /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, F075BEDB1EBB26F100A7B68A /* TableViewCellWithCollectionView.m in Sources */, diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index e454f4bed..23a925b9a 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -467,7 +467,6 @@ "group_invitation_format" = "%@ has invited you to join this community"; // Group participants -"group_participants_title" = "Participants"; "group_participants_add_participant" = "Add participant"; "group_participants_leave_prompt_title" = "Leave group"; "group_participants_leave_prompt_msg" = "Are you sure you want to leave the group?"; @@ -475,12 +474,15 @@ "group_participants_remove_prompt_msg" = "Are you sure you want to remove %@ from this group?"; "group_participants_invite_prompt_title" = "Confirmation"; "group_participants_invite_prompt_msg" = "Are you sure you want to invite %@ to this group?"; -"group_participants_filter_group_members" = "Filter group members"; +"group_participants_filter_members" = "Filter community members"; "group_participants_invite_another_user" = "Search / invite by User ID or Name"; "group_participants_invite_malformed_id_title" = "Invite Error"; "group_participants_invite_malformed_id" = "Malformed ID. Should be a Matrix ID like '@localpart:domain'"; "group_participants_invited_section" = "INVITED"; +// Group rooms +"group_rooms_filter_rooms" = "Filter community rooms"; + // Read Receipts "read_receipts_list" = "Read Receipts List"; "receipt_status_read" = "Read: "; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 499b55cd0..292d4eb2b 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -379,7 +379,6 @@ "group_home_multi_rooms_format" = "%tu salons"; "group_invitation_format" = "%@ vous a invité à rejoindre cette communauté"; // Group participants -"group_participants_title" = "Membres"; "group_participants_add_participant" = "Ajouter un membre"; "group_participants_leave_prompt_title" = "Quitter le groupe"; "group_participants_leave_prompt_msg" = "Voulez-vous vraiment quitter le groupe?"; @@ -387,11 +386,13 @@ "group_participants_remove_prompt_msg" = "Voulez-vous vraiment exclure %@ de ce groupe?"; "group_participants_invite_prompt_title" = "Confirmation"; "group_participants_invite_prompt_msg" = "Voulez-vous vraiment inviter %@ dans ce groupe?"; -"group_participants_filter_group_members" = "Filtrer les membres du group"; +"group_participants_filter_members" = "Filtrer les membres de la communauté"; "group_participants_invite_another_user" = "Rechercher/inviter par identifiant ou nom"; "group_participants_invite_malformed_id_title" = "Erreur lors de l'invitation"; "group_participants_invite_malformed_id" = "Identifiant au mauvais format. Un identifiant Matrix au format \"@utilisateur:domaine\" est attendu"; "group_participants_invited_section" = "INVITÉS"; +// Group rooms +"group_rooms_filter_rooms" = "Filtrer les salons de la communauté"; // Read Receipts "read_receipts_list" = "Liste des accusés de lecture"; "receipt_status_read" = "Lu : "; diff --git a/Riot/ViewController/GroupDetailsViewController.m b/Riot/ViewController/GroupDetailsViewController.m index cad49c910..246ef080b 100644 --- a/Riot/ViewController/GroupDetailsViewController.m +++ b/Riot/ViewController/GroupDetailsViewController.m @@ -15,8 +15,10 @@ */ #import "GroupDetailsViewController.h" + #import "GroupHomeViewController.h" #import "GroupParticipantsViewController.h" +#import "GroupRoomsViewController.h" #import "AppDelegate.h" @@ -24,6 +26,7 @@ { GroupHomeViewController *groupHomeViewController; GroupParticipantsViewController *groupParticipantsViewController; + GroupRoomsViewController *groupRoomsViewController; /** mask view while processing a request @@ -98,7 +101,13 @@ [viewControllers addObject:groupParticipantsViewController]; // Rooms tab - //[titles addObject: NSLocalizedStringFromTable(@"group_details_rooms", @"Vector", nil)]; + [titles addObject: NSLocalizedStringFromTable(@"group_details_rooms", @"Vector", nil)]; + groupRoomsViewController = [GroupRoomsViewController groupRoomsViewController]; + if (_group) + { + [groupRoomsViewController setGroup:_group withMatrixSession:_mxSession]; + } + [viewControllers addObject:groupRoomsViewController]; self.title = NSLocalizedStringFromTable(@"group_details_title", @"Vector", nil); [self initWithTitles:titles viewControllers:viewControllers defaultSelected:0]; @@ -176,6 +185,10 @@ { [groupParticipantsViewController setGroup:group withMatrixSession:mxSession]; } + if (groupRoomsViewController) + { + [groupRoomsViewController setGroup:group withMatrixSession:mxSession]; + } } - (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion diff --git a/Riot/ViewController/GroupParticipantsViewController.m b/Riot/ViewController/GroupParticipantsViewController.m index b2561b2ed..2464ed426 100644 --- a/Riot/ViewController/GroupParticipantsViewController.m +++ b/Riot/ViewController/GroupParticipantsViewController.m @@ -110,9 +110,7 @@ [NSLayoutConstraint activateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; - self.navigationItem.title = NSLocalizedStringFromTable(@"group_participants_title", @"Vector", nil); - - _searchBarView.placeholder = NSLocalizedStringFromTable(@"group_participants_filter_group_members", @"Vector", nil); + _searchBarView.placeholder = NSLocalizedStringFromTable(@"group_participants_filter_members", @"Vector", nil); _searchBarView.returnKeyType = UIReturnKeyDone; _searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone; @@ -350,8 +348,6 @@ // Refresh the members list. [self refreshParticipantsList]; - - [self refreshTableView]; } - (void)startActivityIndicator @@ -562,17 +558,8 @@ actualParticipants = nil; invitedParticipants = nil; } -} - -- (void)reloadSearchResult -{ - if (currentSearchText.length) - { - NSString *searchText = currentSearchText; - currentSearchText = nil; - - [self searchBar:_searchBarView textDidChange:searchText]; - } + + [self finalizeParticipantsList]; } - (void)finalizeParticipantsList @@ -601,7 +588,17 @@ [invitedParticipants sortUsingComparator:comparator]; // Reload search result if any - [self reloadSearchResult]; + if (currentSearchText.length) + { + NSString *searchText = currentSearchText; + currentSearchText = nil; + + [self searchBar:_searchBarView textDidChange:searchText]; + } + else + { + [self refreshTableView]; + } } - (void)addPendingActionMask diff --git a/Riot/ViewController/GroupRoomsViewController.h b/Riot/ViewController/GroupRoomsViewController.h new file mode 100644 index 000000000..34e0bbe9a --- /dev/null +++ b/Riot/ViewController/GroupRoomsViewController.h @@ -0,0 +1,73 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +/** + 'GroupRoomsViewController' instance is used to list the rooms of the group defined by the property 'mxGroup'. + When this property is nil, the view controller is empty. + */ +@interface GroupRoomsViewController : MXKViewController +{ +@protected + + /** + The current list of the rooms. + */ + NSArray *groupRooms; +} + +@property (weak, nonatomic) IBOutlet UITableView *tableView; +@property (weak, nonatomic) IBOutlet UIView *searchBarHeader; +@property (weak, nonatomic) IBOutlet UISearchBar *searchBarView; +@property (weak, nonatomic) IBOutlet UIView *searchBarHeaderBorder; + +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *searchBarTopConstraint; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *tableViewBottomConstraint; + +/** + A matrix group (nil by default). + */ +@property (strong, readonly, nonatomic) MXGroup *group; +@property (strong, readonly, nonatomic) MXSession *mxSession; + +/** + Returns the `UINib` object initialized for a `GroupRoomsViewController`. + + @return The initialized `UINib` object or `nil` if there were errors during initialization + or the nib file could not be located. + */ ++ (UINib *)nib; + +/** + Creates and returns a new `GroupRoomsViewController` object. + + @discussion This is the designated initializer for programmatic instantiation. + @return An initialized `GroupRoomsViewController` object if successful, `nil` otherwise. + */ ++ (instancetype)groupRoomsViewController; + +/** + Set the group for which the rooms are listed. + Provide the related matrix session. + + @param group + @param mxSession + */ +- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession; + +@end + diff --git a/Riot/ViewController/GroupRoomsViewController.m b/Riot/ViewController/GroupRoomsViewController.m new file mode 100644 index 000000000..1b004a070 --- /dev/null +++ b/Riot/ViewController/GroupRoomsViewController.m @@ -0,0 +1,602 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "GroupRoomsViewController.h" + +#import "AppDelegate.h" + +#import "GroupRoomTableViewCell.h" + +#import "RageShakeManager.h" + +@interface GroupRoomsViewController () +{ + // Search result + NSString *currentSearchText; + NSMutableArray *filteredGroupRooms; + + // Observe kRiotDesignValuesDidChangeThemeNotification to handle user interface theme change. + id kRiotDesignValuesDidChangeThemeNotificationObserver; +} + +@end + +@implementation GroupRoomsViewController + +#pragma mark - Class methods + ++ (UINib *)nib +{ + return [UINib nibWithNibName:NSStringFromClass(self.class) + bundle:[NSBundle bundleForClass:self.class]]; +} + ++ (instancetype)groupRoomsViewController +{ + return [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) + bundle:[NSBundle bundleForClass:self.class]]; +} + +#pragma mark - + +- (void)finalizeInit +{ + [super finalizeInit]; + + // Setup `MXKViewControllerHandling` properties + self.enableBarTintColorStatusChange = NO; + self.rageShakeManager = [RageShakeManager sharedManager]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + + // Check whether the view controller has been pushed via storyboard + if (!self.tableView) + { + // Instantiate view controller objects + [[[self class] nib] instantiateWithOwner:self options:nil]; + } + + // Adjust Top and Bottom constraints to take into account potential navBar and tabBar. + [NSLayoutConstraint deactivateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; + + _searchBarTopConstraint = [NSLayoutConstraint constraintWithItem:self.topLayoutGuide + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.searchBarHeader + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + + _tableViewBottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeBottom + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[_searchBarTopConstraint, _tableViewBottomConstraint]]; + + _searchBarView.placeholder = NSLocalizedStringFromTable(@"group_rooms_filter_rooms", @"Vector", nil); + _searchBarView.returnKeyType = UIReturnKeyDone; + _searchBarView.autocapitalizationType = UITextAutocapitalizationTypeNone; + + // Search bar header is hidden when no group is provided + _searchBarHeader.hidden = (self.group == nil); + + // Enable self-sizing cells and section headers. + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 74; + self.tableView.sectionHeaderHeight = 0; + + // Hide line separators of empty cells + self.tableView.tableFooterView = [[UIView alloc] init]; + + [self.tableView registerClass:GroupRoomTableViewCell.class forCellReuseIdentifier:@"RoomTableViewCellId"]; + + // Observe user interface theme change. + kRiotDesignValuesDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kRiotDesignValuesDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self userInterfaceThemeDidChange]; + + }]; + [self userInterfaceThemeDidChange]; +} + +- (void)userInterfaceThemeDidChange +{ + self.defaultBarTintColor = kRiotSecondaryBgColor; + self.barTitleColor = kRiotPrimaryTextColor; + self.activityIndicator.backgroundColor = kRiotOverlayColor; + + [self refreshSearchBarItemsColor:_searchBarView]; + + _searchBarHeaderBorder.backgroundColor = kRiotAuxiliaryColor; + + // Check the table view style to select its bg color. + self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? kRiotPrimaryBgColor : kRiotSecondaryBgColor); + self.view.backgroundColor = self.tableView.backgroundColor; + + if (self.tableView.dataSource) + { + [self.tableView reloadData]; + } +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return kRiotDesignStatusBarStyle; +} + +// This method is called when the viewcontroller is added or removed from a container view controller. +- (void)didMoveToParentViewController:(nullable UIViewController *)parent +{ + [super didMoveToParentViewController:parent]; +} + +- (void)destroy +{ + if (kRiotDesignValuesDidChangeThemeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:kRiotDesignValuesDidChangeThemeNotificationObserver]; + kRiotDesignValuesDidChangeThemeNotificationObserver = nil; + } + + _group = nil; + _mxSession = nil; + + filteredGroupRooms = nil; + + groupRooms = nil; + + [super destroy]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Screen tracking + [[AppDelegate theDelegate] trackScreen:@"GroupDetailsRooms"]; + + if (_group) + { + // Force refresh + [self didUpdateGroupRooms:nil]; + + // Trigger a refresh on the group rooms. + [self.mxSession updateGroupRooms:_group success:nil failure:^(NSError *error) { + + NSLog(@"[GroupRoomsViewController] viewWillAppear: group rooms update failed %@", _group.groupId); + + }]; + } +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + // cancel any pending search + [self searchBarCancelButtonClicked:_searchBarView]; +} + +- (void)withdrawViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion +{ + // Check whether the current view controller is displayed inside a segmented view controller in order to withdraw the right item + if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) + { + [((SegmentedViewController*)self.parentViewController) withdrawViewControllerAnimated:animated completion:completion]; + } + else + { + [super withdrawViewControllerAnimated:animated completion:completion]; + } +} + +- (void)setGroup:(MXGroup*)group withMatrixSession:(MXSession*)mxSession +{ + // Cancel any pending search + [self searchBarCancelButtonClicked:_searchBarView]; + + _mxSession = mxSession; + + [self addMatrixSession:mxSession]; + + [self refreshDisplayWithGroup:group]; +} + +#pragma mark - + +- (void)registerOnGroupChangeNotifications +{ + [self cancelRegistrationOnGroupChangeNotifications]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroupRooms:) name:kMXSessionDidUpdateGroupRoomsNotification object:self.mxSession]; +} + +- (void)cancelRegistrationOnGroupChangeNotifications +{ + // Remove any pending observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)didUpdateGroupRooms:(NSNotification *)notif +{ + // Update here the displayed group instance with the one stored in the session (if any). + MXGroup *group = [self.mxSession groupWithGroupId:_group.groupId]; + + [self refreshDisplayWithGroup:(group ? group : _group)]; +} + +- (void)refreshDisplayWithGroup:(MXGroup *)group +{ + _group = group; + + if (_group) + { + _searchBarHeader.hidden = NO; + + [self registerOnGroupChangeNotifications]; + + groupRooms = _group.rooms.chunk; + } + else + { + // Search bar header is hidden when no group is provided + _searchBarHeader.hidden = YES; + + [self cancelRegistrationOnGroupChangeNotifications]; + + groupRooms = nil; + } + + // Reload search result if any + if (currentSearchText.length) + { + NSString *searchText = currentSearchText; + currentSearchText = nil; + + [self searchBar:_searchBarView textDidChange:searchText]; + } + else + { + [self refreshTableView]; + } +} + +- (void)startActivityIndicator +{ + // Check whether the current view controller is displayed inside a segmented view controller in order to run the right activity view + if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) + { + [((SegmentedViewController*)self.parentViewController) startActivityIndicator]; + + // Force stop the activity view of the view controller + [self.activityIndicator stopAnimating]; + } + else + { + [super startActivityIndicator]; + } +} + +- (void)stopActivityIndicator +{ + // Check whether the current view controller is displayed inside a segmented view controller in order to stop the right activity view + if (self.parentViewController && [self.parentViewController isKindOfClass:SegmentedViewController.class]) + { + [((SegmentedViewController*)self.parentViewController) stopActivityIndicator]; + + // Force stop the activity view of the view controller + [self.activityIndicator stopAnimating]; + } + else + { + [super stopActivityIndicator]; + } +} + +#pragma mark - Internals + +- (void)refreshTableView +{ + [self.tableView reloadData]; +} + +- (void)pushViewController:(UIViewController*)viewController +{ + // Check whether the view controller is displayed inside a segmented one. + if (self.parentViewController.navigationController) + { + // Hide back button title + self.parentViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + [self.parentViewController.navigationController pushViewController:viewController animated:YES]; + } + else + { + // Hide back button title + self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + [self.navigationController pushViewController:viewController animated:YES]; + } +} + +#pragma mark - UITableView data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + NSInteger count = 0; + + if (currentSearchText.length) + { + if (filteredGroupRooms.count) + { + count++; + } + } + else + { + if (groupRooms.count) + { + count++; + } + } + + return count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSInteger count = 0; + + if (currentSearchText.length) + { + count = filteredGroupRooms.count; + } + else + { + count = groupRooms.count; + } + + return count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + GroupRoomTableViewCell* roomCell = [tableView dequeueReusableCellWithIdentifier:@"RoomTableViewCellId" forIndexPath:indexPath]; + roomCell.selectionStyle = UITableViewCellSelectionStyleNone; + + MXGroupRoom *room; + NSArray *rooms; + + if (currentSearchText.length) + { + rooms = filteredGroupRooms; + } + else + { + rooms = groupRooms; + } + + if (indexPath.row < rooms.count) + { + room = rooms[indexPath.row]; + } + + if (room) + { + [roomCell render:room withMatrixSession:self.mxSession]; + } + + return roomCell; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + return NO; +} + +- (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath +{ + // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). +} + +#pragma mark - UITableView delegate + +- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return tableView.estimatedRowHeight; +} + +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath +{ + cell.backgroundColor = kRiotPrimaryBgColor; + + // Update the selected background view + if (kRiotSelectedBgColor) + { + cell.selectedBackgroundView = [[UIView alloc] init]; + cell.selectedBackgroundView.backgroundColor = kRiotSelectedBgColor; + } + else + { + if (tableView.style == UITableViewStylePlain) + { + cell.selectedBackgroundView = nil; + } + else + { + cell.selectedBackgroundView.backgroundColor = nil; + } + } + + // Refresh here the estimated row height + tableView.estimatedRowHeight = cell.frame.size.height; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +//- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath +//{ +// NSMutableArray* actions; +// +// // @TODO Add the swipe to remove this room from the community if the current user is admin +// actions = [[NSMutableArray alloc] init]; +// +// // Patch: Force the width of the button by adding whitespace characters into the title string. +// UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ +// +// // @TODO Remove the room +// +// }]; +// +// leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon_blue" backgroundColor:kRiotSecondaryBgColor patternSize:CGSizeMake(74, 74) resourceSize:CGSizeMake(24, 24)]; +// [actions insertObject:leaveAction atIndex:0]; +// +// return actions; +//} + +#pragma mark - UISearchBar delegate + +- (void)refreshSearchBarItemsColor:(UISearchBar *)searchBar +{ + // bar tint color + searchBar.barTintColor = searchBar.tintColor = kRiotColorBlue; + searchBar.tintColor = kRiotColorBlue; + + // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. + + // text color + UITextField *searchBarTextField = [searchBar valueForKey:@"_searchField"]; + searchBarTextField.textColor = kRiotSecondaryTextColor; + + // Magnifying glass icon. + UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView; + leftImageView.image = [leftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + leftImageView.tintColor = kRiotColorBlue; + + // remove the gray background color + UIView *effectBackgroundTop = [searchBarTextField valueForKey:@"_effectBackgroundTop"]; + UIView *effectBackgroundBottom = [searchBarTextField valueForKey:@"_effectBackgroundBottom"]; + effectBackgroundTop.hidden = YES; + effectBackgroundBottom.hidden = YES; + + // place holder + searchBarTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:searchBarTextField.placeholder + attributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle), + NSUnderlineColorAttributeName: kRiotColorBlue, + NSForegroundColorAttributeName: kRiotColorBlue}]; +} + +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText +{ + // Update search results. + NSUInteger index; + MXGroupRoom *groupRoom; + + searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + if (!currentSearchText.length || [searchText hasPrefix:currentSearchText] == NO) + { + // Copy participants and invited participants + filteredGroupRooms = [NSMutableArray arrayWithArray:groupRooms]; + } + + currentSearchText = searchText; + + // Filter group participants + if (currentSearchText.length) + { + for (index = 0; index < filteredGroupRooms.count;) + { + groupRoom = filteredGroupRooms[index]; + + NSString *displayName = groupRoom.name; + if (!displayName) + { + displayName = groupRoom.canonicalAlias; + } + if (!displayName) + { + displayName = groupRoom.roomId; + } + + if ([displayName rangeOfString:currentSearchText options:NSCaseInsensitiveSearch].location == NSNotFound) + { + [filteredGroupRooms removeObjectAtIndex:index]; + } + else + { + index++; + } + } + } + else + { + filteredGroupRooms = nil; + } + + // Refresh display + [self refreshTableView]; +} + +- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar +{ + searchBar.showsCancelButton = YES; + + return YES; +} + +- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar +{ + searchBar.showsCancelButton = NO; + + return YES; +} + +- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar +{ + // "Done" key has been pressed. + + // Dismiss keyboard + [_searchBarView resignFirstResponder]; +} + +- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar +{ + if (currentSearchText) + { + currentSearchText = nil; + filteredGroupRooms = nil; + + [self refreshTableView]; + } + + searchBar.text = nil; + // Leave search + [searchBar resignFirstResponder]; +} + +@end diff --git a/Riot/ViewController/GroupRoomsViewController.xib b/Riot/ViewController/GroupRoomsViewController.xib new file mode 100644 index 000000000..5e2d861bb --- /dev/null +++ b/Riot/ViewController/GroupRoomsViewController.xib @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Views/RoomList/GroupRoomTableViewCell.h b/Riot/Views/RoomList/GroupRoomTableViewCell.h new file mode 100644 index 000000000..be8dd1ef9 --- /dev/null +++ b/Riot/Views/RoomList/GroupRoomTableViewCell.h @@ -0,0 +1,32 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@interface GroupRoomTableViewCell : MXKTableViewCell + +@property (weak, nonatomic) IBOutlet UILabel *roomDisplayName; +@property (weak, nonatomic) IBOutlet UILabel *roomTopic; +@property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; + +/** + Configure the cell in order to display a room of a group. + + @param groupRoom the room to render. + */ +- (void)render:(MXGroupRoom*)groupRoom withMatrixSession:(MXSession*)mxSession; + +@end diff --git a/Riot/Views/RoomList/GroupRoomTableViewCell.m b/Riot/Views/RoomList/GroupRoomTableViewCell.m new file mode 100644 index 000000000..e57156b52 --- /dev/null +++ b/Riot/Views/RoomList/GroupRoomTableViewCell.m @@ -0,0 +1,92 @@ +/* + Copyright 2017 Vector Creations Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "GroupRoomTableViewCell.h" + +#import "AvatarGenerator.h" + +#import "RiotDesignValues.h" + +@implementation GroupRoomTableViewCell + +- (void)awakeFromNib +{ + [super awakeFromNib]; + + // Round image view + [_roomAvatar.layer setCornerRadius:_roomAvatar.frame.size.width / 2]; + _roomAvatar.clipsToBounds = YES; +} + +- (void)customizeTableViewCellRendering +{ + [super customizeTableViewCellRendering]; + + self.roomDisplayName.textColor = kRiotPrimaryTextColor; + self.roomTopic.textColor = kRiotSecondaryTextColor; + + _roomAvatar.defaultBackgroundColor = [UIColor clearColor]; +} + +- (void)render:(MXGroupRoom *)groupRoom withMatrixSession:(MXSession*)mxSession +{ + // Set room display name + self.roomDisplayName.text = groupRoom.name; + if (!self.roomDisplayName.text) + { + self.roomDisplayName.text = groupRoom.canonicalAlias; + } + if (!self.roomDisplayName.text) + { + self.roomDisplayName.text = groupRoom.roomId; + } + + // Check whether this room has topic + if (groupRoom.topic) + { + _roomTopic.hidden = NO; + _roomTopic.text = [MXTools stripNewlineCharacters:groupRoom.topic]; + } + else + { + // Hide and fill the label with a fake description to harmonize the height of all the cells. + // This is a drawback of the self-sizing cell. + _roomTopic.hidden = YES; + _roomTopic.text = @"No topic"; + } + + // Set the avatar + UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:groupRoom.roomId withDisplayName:self.roomDisplayName.text]; + + if (groupRoom.avatarUrl) + { + _roomAvatar.enableInMemoryCache = YES; + + [_roomAvatar setImageURL:[mxSession.matrixRestClient urlOfContentThumbnail:groupRoom.avatarUrl + toFitViewSize:_roomAvatar.frame.size + withMethod:MXThumbnailingMethodCrop] + withType:nil + andImageOrientation:UIImageOrientationUp previewImage:avatarImage]; + } + else + { + _roomAvatar.image = avatarImage; + } + + _roomAvatar.contentMode = UIViewContentModeScaleAspectFill; +} + +@end diff --git a/Riot/Views/RoomList/GroupRoomTableViewCell.xib b/Riot/Views/RoomList/GroupRoomTableViewCell.xib new file mode 100644 index 000000000..33dbfa4c6 --- /dev/null +++ b/Riot/Views/RoomList/GroupRoomTableViewCell.xib @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +