diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index d33f003c1..1ca836458 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 325072141E8C0AC900A084B6 /* LaunchScreenLogo.png in Resources */ = {isa = PBXBuildFile; fileRef = 325072131E8C0AC900A084B6 /* LaunchScreenLogo.png */; }; 325E1C151E8D03950018D91E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 325E1C131E8D03950018D91E /* LaunchScreen.storyboard */; }; E2EAC1A4FBD6FE5228584591 /* libPods-Riot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D8737F782E108CFD6908691 /* libPods-Riot.a */; }; + F02C1A861E8EB04C0045A404 /* PeopleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F02C1A841E8EB04C0045A404 /* PeopleViewController.m */; }; F05BD79E1E7AEBF800C69941 /* UnifiedSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F05BD79D1E7AEBF800C69941 /* UnifiedSearchViewController.m */; }; F05BD7A11E7C0E4500C69941 /* MasterTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = F05BD7A01E7C0E4500C69941 /* MasterTabBarController.m */; }; F083BD1D1E7009ED00A9B29C /* RageShakeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BB0B1E7009EC00A9B29C /* RageShakeManager.m */; }; @@ -402,6 +403,9 @@ F083BEA21E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BD1B1E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.m */; }; F083BEA31E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = F083BD1C1E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.xib */; }; F083BEA51E70356E00A9B29C /* RiotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BB041E7005FD00A9B29C /* RiotTests.m */; }; + F0E059FD1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = F0E059FC1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.m */; }; + F0E05A021E963103004B83FB /* FavouritesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0E059FF1E963103004B83FB /* FavouritesViewController.m */; }; + F0E05A031E963103004B83FB /* RoomsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0E05A011E963103004B83FB /* RoomsViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -419,6 +423,8 @@ 325072131E8C0AC900A084B6 /* LaunchScreenLogo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = LaunchScreenLogo.png; path = Assets/Images/LaunchScreenLogo.png; sourceTree = ""; }; 325E1C141E8D03950018D91E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 7D8737F782E108CFD6908691 /* libPods-Riot.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Riot.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F02C1A831E8EB04C0045A404 /* PeopleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PeopleViewController.h; sourceTree = ""; }; + F02C1A841E8EB04C0045A404 /* PeopleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PeopleViewController.m; sourceTree = ""; }; F05BD79C1E7AEBF800C69941 /* UnifiedSearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnifiedSearchViewController.h; sourceTree = ""; }; F05BD79D1E7AEBF800C69941 /* UnifiedSearchViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnifiedSearchViewController.m; sourceTree = ""; }; F05BD79F1E7C0E4500C69941 /* MasterTabBarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterTabBarController.h; sourceTree = ""; }; @@ -926,6 +932,12 @@ F083BD1C1E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TableViewCellWithPhoneNumberTextField.xib; sourceTree = ""; }; F094A9A21B78D8F000B1FBBF /* Riot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Riot.app; sourceTree = BUILT_PRODUCTS_DIR; }; F094A9BE1B78D8F000B1FBBF /* RiotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RiotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F0E059FB1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnifiedSearchRecentsDataSource.h; sourceTree = ""; }; + F0E059FC1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UnifiedSearchRecentsDataSource.m; sourceTree = ""; }; + F0E059FE1E963103004B83FB /* FavouritesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FavouritesViewController.h; sourceTree = ""; }; + F0E059FF1E963103004B83FB /* FavouritesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FavouritesViewController.m; sourceTree = ""; }; + F0E05A001E963103004B83FB /* RoomsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomsViewController.h; sourceTree = ""; }; + F0E05A011E963103004B83FB /* RoomsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomsViewController.m; sourceTree = ""; }; F9D678EF54918C036FDEDBF9 /* Pods-Riot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riot.release.xcconfig"; path = "Pods/Target Support Files/Pods-Riot/Pods-Riot.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1302,6 +1314,8 @@ F083BC071E7009EC00A9B29C /* RecentCellData.m */, F083BC081E7009EC00A9B29C /* RecentsDataSource.h */, F083BC091E7009EC00A9B29C /* RecentsDataSource.m */, + F0E059FB1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.h */, + F0E059FC1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.m */, ); path = RoomList; sourceTree = ""; @@ -1333,6 +1347,12 @@ F083BC191E7009EC00A9B29C /* ViewController */ = { isa = PBXGroup; children = ( + F0E059FE1E963103004B83FB /* FavouritesViewController.h */, + F0E059FF1E963103004B83FB /* FavouritesViewController.m */, + F0E05A001E963103004B83FB /* RoomsViewController.h */, + F0E05A011E963103004B83FB /* RoomsViewController.m */, + F02C1A831E8EB04C0045A404 /* PeopleViewController.h */, + F02C1A841E8EB04C0045A404 /* PeopleViewController.m */, F05BD79F1E7C0E4500C69941 /* MasterTabBarController.h */, F05BD7A01E7C0E4500C69941 /* MasterTabBarController.m */, F05BD79C1E7AEBF800C69941 /* UnifiedSearchViewController.h */, @@ -2218,6 +2238,7 @@ F083BE881E7009ED00A9B29C /* RoomMemberTitleView.m in Sources */, F083BE701E7009ED00A9B29C /* RoomOutgoingTextMsgBubbleCell.m in Sources */, F083BE7C1E7009ED00A9B29C /* DirectoryRecentTableViewCell.m in Sources */, + F0E05A031E963103004B83FB /* RoomsViewController.m in Sources */, F083BE801E7009ED00A9B29C /* PublicRoomTableViewCell.m in Sources */, F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */, F083BE4A1E7009ED00A9B29C /* RoomOutgoingEncryptedAttachmentBubbleCell.m in Sources */, @@ -2238,6 +2259,7 @@ F083BDED1E7009ED00A9B29C /* MXKRoomBubbleTableViewCell+Riot.m in Sources */, F05BD79E1E7AEBF800C69941 /* UnifiedSearchViewController.m in Sources */, F083BE561E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, + F02C1A861E8EB04C0045A404 /* PeopleViewController.m in Sources */, F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */, F083BE151E7009ED00A9B29C /* MediaAlbumContentViewController.m in Sources */, F083BE5C1E7009ED00A9B29C /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, @@ -2271,6 +2293,7 @@ F083BE9A1E7009ED00A9B29C /* TableViewCellWithButton.m in Sources */, F083BE8E1E7009ED00A9B29C /* RoomAvatarTitleView.m in Sources */, F05BD7A11E7C0E4500C69941 /* MasterTabBarController.m in Sources */, + F0E059FD1E9545BB004B83FB /* UnifiedSearchRecentsDataSource.m in Sources */, F083BE4E1E7009ED00A9B29C /* RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */, F083BE6C1E7009ED00A9B29C /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, F083BE681E7009ED00A9B29C /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, @@ -2284,6 +2307,7 @@ F083BDF91E7009ED00A9B29C /* RoomEmailInvitation.m in Sources */, F083BE341E7009ED00A9B29C /* EncryptionInfoView.m in Sources */, F083BE641E7009ED00A9B29C /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.m in Sources */, + F0E05A021E963103004B83FB /* FavouritesViewController.m in Sources */, F083BE941E7009ED00A9B29C /* FilesSearchTableViewCell.m in Sources */, F083BE921E7009ED00A9B29C /* SimpleRoomTitleView.m in Sources */, F083BE981E7009ED00A9B29C /* MessagesSearchResultTextMsgBubbleCell.m in Sources */, diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 194f3822c..0d21bc183 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -17,6 +17,9 @@ // Titles "title_home" = "Home"; +"title_favourites" = "Favourites"; +"title_people" = "People"; +"title_rooms" = "Rooms"; "warning" = "Warning"; // Actions @@ -121,6 +124,15 @@ "room_recents_start_chat_with" = "Start chat"; "room_recents_create_empty_room" = "Create room"; +// People tab +"people_invites_section" = "INVITES (%tu)"; +"people_conversation_section" = "CONVERSATIONS (%tu)"; +"people_address_book_section" = "LOCAL CONTACTS (%tu)"; +"people_address_book_matrix_users_toggle" = "Matrix users only"; +"people_matrix_users_section" = "KNOWN CONTACTS (%tu)"; +"people_matrix_users_default_section" = "KNOWN CONTACTS (-)"; +"people_matrix_users_search_prompt" = "Too many contacts, please use the search field"; + // Search "search_rooms" = "Rooms"; "search_messages" = "Messages"; diff --git a/Riot/Base.lproj/Main.storyboard b/Riot/Base.lproj/Main.storyboard index 1a68197a6..b93f1d1dd 100644 --- a/Riot/Base.lproj/Main.storyboard +++ b/Riot/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -155,6 +155,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -174,7 +215,7 @@ - + @@ -189,12 +230,15 @@ - + + + + - + @@ -222,9 +266,11 @@ - + + + @@ -298,7 +344,7 @@ - + @@ -362,6 +408,25 @@ + + + + + + + + + + + + + + + + + + + @@ -378,7 +443,7 @@ - + @@ -397,7 +462,7 @@ - + @@ -424,8 +489,9 @@ - + + diff --git a/Riot/Model/RoomList/RecentsDataSource.h b/Riot/Model/RoomList/RecentsDataSource.h index 9a3a8629b..a945ce261 100644 --- a/Riot/Model/RoomList/RecentsDataSource.h +++ b/Riot/Model/RoomList/RecentsDataSource.h @@ -1,5 +1,6 @@ /* Copyright 2015 OpenMarket Ltd + 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. @@ -16,12 +17,72 @@ #import -@class PublicRoomsDirectoryDataSource; +#import "PublicRoomsDirectoryDataSource.h" /** - 'RecentsDataSource' class inherits from 'MXKInterleavedRecentsDataSource' to define Vector recents source. + List the different modes used to prepare the recents data source. + Each mode corresponds to an application tab: Home, Favourites, People and Rooms. + */ +typedef enum : NSUInteger +{ + RecentsDataSourceModeHome, + RecentsDataSourceModeFavourites, + RecentsDataSourceModePeople, + RecentsDataSourceModeRooms + +} RecentsDataSourceMode; + +/** + 'RecentsDataSource' class inherits from 'MXKInterleavedRecentsDataSource' to define the Riot recents source + shared between all the applications tabs. */ @interface RecentsDataSource : MXKInterleavedRecentsDataSource +{ +@protected + NSInteger directorySection; + NSInteger invitesSection; + NSInteger favoritesSection; + NSInteger conversationSection; + NSInteger lowPrioritySection; + + NSInteger sectionsCount; +} + +/** + Set the delegate by specifying the selected display mode. + */ +- (void)setDelegate:(id)delegate andRecentsDataSourceMode:(RecentsDataSourceMode)recentsDataSourceMode; + +/** + The current mode (RecentsDataSourceModeHome by default). + */ +@property (nonatomic, readonly) RecentsDataSourceMode recentsDataSourceMode; + +/** + The data source used to manage the rooms from directory. + */ +@property (nonatomic) PublicRoomsDirectoryDataSource *publicRoomsDirectoryDataSource; + +/** + Refresh the recents data source and notify its delegate. + */ +- (void)forceRefresh; + +/** + Tell whether the sections are shrinkable. NO by default. + */ +@property (nonatomic) BOOL areSectionsShrinkable; + +#pragma mark - Drag & Drop handling +/** + Return true of the cell can be moved from a section to another one. + */ +- (BOOL)isDraggableCellAt:(NSIndexPath*)path; + +/** + Return true of the cell can be moved from a section to another one. + */ +- (BOOL)canCellMoveFrom:(NSIndexPath*)oldPath to:(NSIndexPath*)newPath; /** There is a pending drag and drop cell. @@ -40,36 +101,6 @@ */ @property (nonatomic) UIImageView* droppingCellBackGroundView; -/** - The data source used to manage search in public rooms. - */ -@property (nonatomic, readonly) PublicRoomsDirectoryDataSource *publicRoomsDirectoryDataSource; - -/** - Hide the public rooms directory cell. YES by default. - */ -@property (nonatomic) BOOL hidePublicRoomsDirectory; - -/** - Hide recents. NO by default. - */ -@property (nonatomic) BOOL hideRecents; - -/** - Return the header height from the section. - */ -- (CGFloat)heightForHeaderInSection:(NSInteger)section; - -/** - Return true of the cell can be moved from a section to another one. - */ -- (BOOL)isDraggableCellAt:(NSIndexPath*)path; - -/** - Return true of the cell can be moved from a section to another one. - */ -- (BOOL)canCellMoveFrom:(NSIndexPath*)oldPath to:(NSIndexPath*)newPath; - /** Move a cell from a path to another one. It is based on room Tag. diff --git a/Riot/Model/RoomList/RecentsDataSource.m b/Riot/Model/RoomList/RecentsDataSource.m index 8e577cbf3..e4541fdb8 100644 --- a/Riot/Model/RoomList/RecentsDataSource.m +++ b/Riot/Model/RoomList/RecentsDataSource.m @@ -17,19 +17,12 @@ #import "RecentsDataSource.h" -#import "EventFormatter.h" +#import "RecentCellData.h" #import "RiotDesignValues.h" -#import "RoomIdOrAliasTableViewCell.h" -#import "DirectoryRecentTableViewCell.h" - -#import "PublicRoomsDirectoryDataSource.h" - #import "MXRoom+Riot.h" -#import "RecentCellData.h" - #define RECENTSDATASOURCE_SECTION_DIRECTORY 0x01 #define RECENTSDATASOURCE_SECTION_INVITES 0x02 #define RECENTSDATASOURCE_SECTION_FAVORITES 0x04 @@ -42,22 +35,11 @@ NSMutableArray* favoriteCellDataArray; NSMutableArray* conversationCellDataArray; NSMutableArray* lowPriorityCellDataArray; - - NSInteger searchedRoomIdOrAliasSection; // used to display the potential room id or alias typed during search. - NSInteger directorySection; - NSInteger invitesSection; - NSInteger favoritesSection; - NSInteger conversationSection; - NSInteger lowPrioritySection; - NSInteger sectionsCount; NSInteger shrinkedSectionsBitMask; NSMutableDictionary *roomTagsListenerByUserId; - // The potential room id or alias typed in search input. - NSString *roomIdOrAlias; - // Timer to not refresh publicRoomsDirectoryDataSource on every keystroke. NSTimer *publicRoomsTriggerTimer; } @@ -71,11 +53,11 @@ self = [super init]; if (self) { + invitesCellDataArray = [[NSMutableArray alloc] init]; favoriteCellDataArray = [[NSMutableArray alloc] init]; - conversationCellDataArray = [[NSMutableArray alloc] init]; lowPriorityCellDataArray = [[NSMutableArray alloc] init]; + conversationCellDataArray = [[NSMutableArray alloc] init]; - searchedRoomIdOrAliasSection = -1; directorySection = -1; invitesSection = -1; favoritesSection = -1; @@ -83,9 +65,7 @@ lowPrioritySection = -1; sectionsCount = 0; - _hideRecents = NO; - _hidePublicRoomsDirectory = YES; - + _areSectionsShrinkable = NO; shrinkedSectionsBitMask = 0; roomTagsListenerByUserId = [[NSMutableDictionary alloc] init]; @@ -96,6 +76,23 @@ return self; } +#pragma mark - + +- (void)setDelegate:(id)delegate andRecentsDataSourceMode:(RecentsDataSourceMode)recentsDataSourceMode +{ + self.delegate = delegate; + + self.recentsDataSourceMode = recentsDataSourceMode; +} + +- (void)setRecentsDataSourceMode:(RecentsDataSourceMode)recentsDataSourceMode +{ + _recentsDataSourceMode = recentsDataSourceMode; + + [self forceRefresh]; +} + +#pragma mark - - (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession *)mxSession { @@ -139,7 +136,7 @@ { if (dataSource == _publicRoomsDirectoryDataSource) { - [self refreshRoomsSectionsAndReload]; + [self forceRefresh]; } else { @@ -156,7 +153,7 @@ { dispatch_async(dispatch_get_main_queue(), ^{ - [self refreshRoomsSectionsAndReload]; + [self forceRefresh]; }); } @@ -168,7 +165,7 @@ } } -- (void)refreshRoomsSectionsAndReload +- (void)forceRefresh { // Refresh is disabled during drag&drop animation" if (!self.droppingCellIndexPath) @@ -185,87 +182,30 @@ MXSession *mxSession = notif.object; if ([self.mxSessions indexOfObject:mxSession] != NSNotFound) { - [self refreshRoomsSectionsAndReload]; - } -} - -#pragma mark - - -- (void)setHidePublicRoomsDirectory:(BOOL)hidePublicRoomsDirectory -{ - if (_hidePublicRoomsDirectory != hidePublicRoomsDirectory) - { - _hidePublicRoomsDirectory = hidePublicRoomsDirectory; - - if (!_hidePublicRoomsDirectory) - { - // Start by looking for all public rooms - self.publicRoomsDirectoryDataSource.searchPattern = nil; - } - - [self refreshRoomsSectionsAndReload]; - } -} - -- (void)setHideRecents:(BOOL)hideRecents -{ - if (_hideRecents != hideRecents) - { - _hideRecents = hideRecents; - - [self refreshRoomsSectionsAndReload]; + [self forceRefresh]; } } #pragma mark - UITableViewDataSource -/** - Return the header height from the section. - */ -- (CGFloat)heightForHeaderInSection:(NSInteger)section -{ - if ((section == directorySection) || (section == invitesSection) || (section == favoritesSection) || (section == conversationSection) || (section == lowPrioritySection)) - { - return 30.0f; - } - - return 0.0f; -} - - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Check whether all data sources are ready before rendering recents if (self.state == MXKDataSourceStateReady) { - // Only one section is handled by this data source. + // Return the last updated number of sections. return sectionsCount; } return 0; } -- (BOOL)isMovingCellSection:(NSInteger)section -{ - return self.droppingCellIndexPath && (self.droppingCellIndexPath.section == section); -} - -- (BOOL)isHiddenCellSection:(NSInteger)section -{ - return self.hiddenCellIndexPath && (self.hiddenCellIndexPath.section == section); -} - - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSUInteger count = 0; - - if (section == searchedRoomIdOrAliasSection) - { - count = 1; - } - else if (section == directorySection) - { - count = 1; - } - else if (section == favoritesSection && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_FAVORITES)) + + //TODO: directorySection + + if (section == favoritesSection && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_FAVORITES)) { count = favoriteCellDataArray.count; } @@ -282,6 +222,7 @@ count = invitesCellDataArray.count; } + // Adjust this count according to the potential dragged cell. if ([self isMovingCellSection:section]) { count++; @@ -299,35 +240,33 @@ { UIView *sectionHeader = nil; - if (section < sectionsCount && section != searchedRoomIdOrAliasSection) + // TODO header for directorySection + + if (section < sectionsCount) { NSString* sectionTitle = @""; NSInteger sectionBitwise = 0; UIImageView *chevronView; - if (section == directorySection) - { - sectionTitle = NSLocalizedStringFromTable(@"room_recents_directory", @"Vector", nil); - } - else if (section == favoritesSection) + if (section == favoritesSection) { sectionTitle = NSLocalizedStringFromTable(@"room_recents_favourites", @"Vector", nil); - sectionBitwise = RECENTSDATASOURCE_SECTION_FAVORITES; + sectionBitwise = _areSectionsShrinkable ? RECENTSDATASOURCE_SECTION_FAVORITES : 0; } else if (section == conversationSection) { sectionTitle = NSLocalizedStringFromTable(@"room_recents_conversations", @"Vector", nil); - sectionBitwise = RECENTSDATASOURCE_SECTION_CONVERSATIONS; + sectionBitwise = _areSectionsShrinkable ? RECENTSDATASOURCE_SECTION_CONVERSATIONS : 0; } else if (section == lowPrioritySection) { sectionTitle = NSLocalizedStringFromTable(@"room_recents_low_priority", @"Vector", nil); - sectionBitwise = RECENTSDATASOURCE_SECTION_LOWPRIORITY; + sectionBitwise = _areSectionsShrinkable ? RECENTSDATASOURCE_SECTION_LOWPRIORITY : 0; } else if (section == invitesSection) { sectionTitle = NSLocalizedStringFromTable(@"room_recents_invites", @"Vector", nil); - sectionBitwise = RECENTSDATASOURCE_SECTION_INVITES; + sectionBitwise = _areSectionsShrinkable ? RECENTSDATASOURCE_SECTION_INVITES : 0; } sectionHeader = [[UIView alloc] initWithFrame:frame]; @@ -384,38 +323,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == searchedRoomIdOrAliasSection) - { - RoomIdOrAliasTableViewCell *roomIdOrAliasCell = [tableView dequeueReusableCellWithIdentifier:RoomIdOrAliasTableViewCell.defaultReuseIdentifier]; - if (!roomIdOrAliasCell) - { - roomIdOrAliasCell = [[RoomIdOrAliasTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[RoomIdOrAliasTableViewCell defaultReuseIdentifier]]; - } - - [roomIdOrAliasCell render:roomIdOrAlias]; - - return roomIdOrAliasCell; - } - else if (indexPath.section == directorySection) - { - // For the cell showing the public rooms directory search result, - // skip the MatrixKit mechanism and return directly the UITableViewCell - DirectoryRecentTableViewCell *directoryCell = [tableView dequeueReusableCellWithIdentifier:DirectoryRecentTableViewCell.defaultReuseIdentifier]; - if (!directoryCell) - { - directoryCell = [[DirectoryRecentTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[DirectoryRecentTableViewCell defaultReuseIdentifier]]; - } - - [directoryCell render:_publicRoomsDirectoryDataSource]; - - return directoryCell; - } - + // TODO: cell for directorySection + if (self.droppingCellIndexPath && [indexPath isEqual:self.droppingCellIndexPath]) { - static NSString* cellIdentifier = @"VectorRecentsMovingCell"; + static NSString* cellIdentifier = @"RiotRecentsMovingCell"; - UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"VectorRecentsMovingCell"]; + UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"RiotRecentsMovingCell"]; // add an imageview of the cell. // The image is a shot of the genuine cell. @@ -492,17 +406,7 @@ - (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == searchedRoomIdOrAliasSection) - { - return RoomIdOrAliasTableViewCell.cellHeight; - } - - if (indexPath.section == directorySection) - { - // For the cell showing the public rooms directory search result, - // skip the MatrixKit mechanism and return directly the cell height - return DirectoryRecentTableViewCell.cellHeight; - } + // TODO: cell height for directorySection if (self.droppingCellIndexPath && [indexPath isEqual:self.droppingCellIndexPath]) { @@ -522,6 +426,8 @@ return 0; } +#pragma mark - + - (NSInteger)cellIndexPosWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession within:(NSMutableArray*)cellDataArray { if (roomId && matrixSession && cellDataArray.count) @@ -611,110 +517,130 @@ #pragma mark - MXKDataSourceDelegate -// create an array filled with NSNull and with the same size as sourceArray -- (NSMutableArray*)createEmptyArray:(NSUInteger)count -{ - NSMutableArray* array = [[NSMutableArray alloc] init]; - - for(NSUInteger i = 0; i < count; i++) - { - [array addObject:[NSNull null]]; - } - - return array; -} - - (void)refreshRoomsSections { - // FIXME manage multi accounts - favoriteCellDataArray = [[NSMutableArray alloc] init]; - conversationCellDataArray = [[NSMutableArray alloc] init]; - lowPriorityCellDataArray = [[NSMutableArray alloc] init]; + [invitesCellDataArray removeAllObjects]; + [favoriteCellDataArray removeAllObjects]; + [conversationCellDataArray removeAllObjects]; + [lowPriorityCellDataArray removeAllObjects]; - searchedRoomIdOrAliasSection = directorySection = favoritesSection = conversationSection = lowPrioritySection = invitesSection = -1; + directorySection = favoritesSection = conversationSection = lowPrioritySection = invitesSection = -1; sectionsCount = 0; - if (roomIdOrAlias.length) - { - // The current search pattern corresponds to a valid room id or room alias - searchedRoomIdOrAliasSection = sectionsCount++; - } - - if (!_hidePublicRoomsDirectory) - { - // The public rooms directory cell is then visible whatever the search activity. - directorySection = sectionsCount++; - } - - if (!_hideRecents && displayedRecentsDataSourceArray.count > 0) + if (displayedRecentsDataSourceArray.count > 0) { + // FIXME manage multi accounts MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:0]; MXSession* session = recentsDataSource.mxSession; - NSArray* sortedInvitesRooms = [session invitedRooms]; - NSArray* sortedFavRooms = [session roomsWithTag:kMXRoomTagFavourite]; - NSArray* sortedLowPriorRooms = [session roomsWithTag:kMXRoomTagLowPriority]; - - invitesCellDataArray = [self createEmptyArray:sortedInvitesRooms.count]; - favoriteCellDataArray = [self createEmptyArray:sortedFavRooms.count]; - lowPriorityCellDataArray = [self createEmptyArray:sortedLowPriorRooms.count]; - NSInteger count = recentsDataSource.numberOfCells; - for(int index = 0; index < count; index++) + if (_recentsDataSourceMode == RecentsDataSourceModeHome) { - NSUInteger pos; - id recentCellDataStoring = [recentsDataSource cellDataAtIndex:index]; - MXRoom* room = recentCellDataStoring.roomSummary.room; - - if ((pos = [sortedFavRooms indexOfObject:room]) != NSNotFound) + for (int index = 0; index < count; index++) { - if (pos < favoriteCellDataArray.count) + id recentCellDataStoring = [recentsDataSource cellDataAtIndex:index]; + MXRoom* room = recentCellDataStoring.roomSummary.room; + + if (room.accountData.tags[kMXRoomTagFavourite]) { - [favoriteCellDataArray replaceObjectAtIndex:pos withObject:recentCellDataStoring]; + [favoriteCellDataArray addObject:recentCellDataStoring]; } - } - else if ((pos = [sortedLowPriorRooms indexOfObject:room]) != NSNotFound) - { - if (pos < lowPriorityCellDataArray.count) + else if (room.accountData.tags[kMXRoomTagLowPriority]) { - [lowPriorityCellDataArray replaceObjectAtIndex:pos withObject:recentCellDataStoring]; + [lowPriorityCellDataArray addObject:recentCellDataStoring]; } - } - else if ((pos = [sortedInvitesRooms indexOfObject:room]) != NSNotFound) - { - if (pos < invitesCellDataArray.count) + else if (room.state.membership == MXMembershipInvite) { - [invitesCellDataArray replaceObjectAtIndex:pos withObject:recentCellDataStoring]; + [invitesCellDataArray addObject:recentCellDataStoring]; + } + else + { + [conversationCellDataArray addObject:recentCellDataStoring]; } - } - else - { - [conversationCellDataArray addObject:recentCellDataStoring]; } } - - [invitesCellDataArray removeObject:[NSNull null]]; + else if (_recentsDataSourceMode == RecentsDataSourceModeFavourites) + { + for (int index = 0; index < count; index++) + { + id recentCellDataStoring = [recentsDataSource cellDataAtIndex:index]; + MXRoom* room = recentCellDataStoring.roomSummary.room; + + // Keep only the favourites rooms. + if (room.accountData.tags[kMXRoomTagFavourite]) + { + [favoriteCellDataArray addObject:recentCellDataStoring]; + } + } + } + else if (_recentsDataSourceMode == RecentsDataSourceModePeople) + { + for (int index = 0; index < count; index++) + { + id recentCellDataStoring = [recentsDataSource cellDataAtIndex:index]; + MXRoom* room = recentCellDataStoring.roomSummary.room; + + // Keep only the direct rooms. + if (room.isDirect) + { + if (room.state.membership == MXMembershipInvite) + { + [invitesCellDataArray addObject:recentCellDataStoring]; + } + else + { + [conversationCellDataArray addObject:recentCellDataStoring]; + } + } + } + } + else if (_recentsDataSourceMode == RecentsDataSourceModeRooms) + { + for (int index = 0; index < count; index++) + { + id recentCellDataStoring = [recentsDataSource cellDataAtIndex:index]; + MXRoom* room = recentCellDataStoring.roomSummary.room; + + // Keep only the rooms without tag + if (!room.accountData.tags.count) + { + [conversationCellDataArray addObject:recentCellDataStoring]; + } + } + + // TODO: Add Directory section. + } + if (invitesCellDataArray.count > 0) { invitesSection = sectionsCount++; } - [favoriteCellDataArray removeObject:[NSNull null]]; if (favoriteCellDataArray.count > 0) { + // Sort them according to their tag order + [favoriteCellDataArray sortUsingComparator:^NSComparisonResult(id recentCellData1, id recentCellData2) { + + return [session compareRoomsByTag:kMXRoomTagFavourite room1:recentCellData1.roomSummary.room room2:recentCellData2.roomSummary.room]; + + }]; favoritesSection = sectionsCount++; } - [conversationCellDataArray removeObject:[NSNull null]]; if (conversationCellDataArray.count > 0) { conversationSection = sectionsCount++; } - [lowPriorityCellDataArray removeObject:[NSNull null]]; if (lowPriorityCellDataArray.count > 0) { + // Sort them according to their tag order + [lowPriorityCellDataArray sortUsingComparator:^NSComparisonResult(id recentCellData1, id recentCellData2) { + + return [session compareRoomsByTag:kMXRoomTagLowPriority room1:recentCellData1.roomSummary.room room2:recentCellData2.roomSummary.room]; + + }]; lowPrioritySection = sectionsCount++; } } @@ -746,6 +672,18 @@ [super dataSource:dataSource didCellChange:changes]; } +#pragma mark - Drag & Drop handling + +- (BOOL)isMovingCellSection:(NSInteger)section +{ + return self.droppingCellIndexPath && (self.droppingCellIndexPath.section == section); +} + +- (BOOL)isHiddenCellSection:(NSInteger)section +{ + return self.hiddenCellIndexPath && (self.hiddenCellIndexPath.section == section); +} + #pragma mark - Action - (IBAction)onButtonPressed:(id)sender @@ -795,25 +733,9 @@ } #pragma mark - Override MXKRecentsDataSource + - (void)searchWithPatterns:(NSArray *)patternsList { - // Check whether the typed input is a room alias or a room identifier. - roomIdOrAlias = nil; - if (patternsList.count == 1) - { - NSString *pattern = patternsList[0]; - - if ([MXTools isMatrixRoomAlias:pattern] || [MXTools isMatrixRoomIdentifier:pattern]) - { - // Display this room id/alias only if it is not already joined by the user - MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; - if (![accountManager accountKnowingRoomWithRoomIdOrAlias:pattern]) - { - roomIdOrAlias = pattern; - } - } - } - [super searchWithPatterns:patternsList]; if (_publicRoomsDirectoryDataSource) @@ -830,25 +752,18 @@ - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { // Invited rooms are not editable. - MXRoom* room = [self getRoomAtIndexPath:indexPath]; - if (room) - { - NSArray* invitedRooms = room.mxSession.invitedRooms; - - // Display no action for the invited room - if (invitedRooms && ([invitedRooms indexOfObject:room] != NSNotFound)) - { - return NO; - } - } - - return YES; + return (indexPath.section != invitesSection); } #pragma mark - drag and drop managemenent - (BOOL)isDraggableCellAt:(NSIndexPath*)path { + if (_recentsDataSourceMode == RecentsDataSourceModePeople || _recentsDataSourceMode == RecentsDataSourceModeRooms) + { + return NO; + } + return (path && ((path.section == favoritesSection) || (path.section == lowPrioritySection) || (path.section == conversationSection))); } @@ -919,7 +834,7 @@ moveFailure(error); } - [self refreshRoomsSectionsAndReload]; + [self forceRefresh]; // Notify MatrixKit user [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; @@ -934,7 +849,7 @@ moveFailure(nil); } - [self refreshRoomsSectionsAndReload]; + [self forceRefresh]; } } diff --git a/Riot/Model/RoomList/UnifiedSearchRecentsDataSource.h b/Riot/Model/RoomList/UnifiedSearchRecentsDataSource.h new file mode 100644 index 000000000..6102584de --- /dev/null +++ b/Riot/Model/RoomList/UnifiedSearchRecentsDataSource.h @@ -0,0 +1,34 @@ +/* + 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 + +#import "RecentsDataSource.h" + +/** + 'UnifiedSearchRecentsDataSource' class inherits from 'RecentsDataSource' to define the Riot recents source + used during the unified search on rooms. + */ +@interface UnifiedSearchRecentsDataSource : RecentsDataSource + +#pragma mark - Directory handling + +/** + Hide recents. NO by default. + */ +@property (nonatomic) BOOL hideRecents; + +@end diff --git a/Riot/Model/RoomList/UnifiedSearchRecentsDataSource.m b/Riot/Model/RoomList/UnifiedSearchRecentsDataSource.m new file mode 100644 index 000000000..5ab2c1f8c --- /dev/null +++ b/Riot/Model/RoomList/UnifiedSearchRecentsDataSource.m @@ -0,0 +1,243 @@ +/* + 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 "UnifiedSearchRecentsDataSource.h" + +#import "RiotDesignValues.h" + +#import "RoomIdOrAliasTableViewCell.h" +#import "DirectoryRecentTableViewCell.h" + +#import "MXRoom+Riot.h" + +@interface UnifiedSearchRecentsDataSource() +{ + NSInteger searchedRoomIdOrAliasSection; // used to display the potential room id or alias typed during search. + + // The potential room id or alias typed in search input. + NSString *roomIdOrAlias; +} +@end + +@implementation UnifiedSearchRecentsDataSource + +- (instancetype)init +{ + self = [super init]; + if (self) + { + searchedRoomIdOrAliasSection = -1; + + _hideRecents = NO; + } + return self; +} + +#pragma mark - + +- (void)setPublicRoomsDirectoryDataSource:(PublicRoomsDirectoryDataSource *)publicRoomsDirectoryDataSource +{ + [super setPublicRoomsDirectoryDataSource:publicRoomsDirectoryDataSource]; + + // Start by looking for all public rooms + self.publicRoomsDirectoryDataSource.searchPattern = nil; +} + +- (void)setHideRecents:(BOOL)hideRecents +{ + if (_hideRecents != hideRecents) + { + _hideRecents = hideRecents; + + [self forceRefresh]; + } +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + // Check whether all data sources are ready before rendering recents + if (self.state == MXKDataSourceStateReady) + { + NSInteger sectionsOffset = 0; + + if (roomIdOrAlias.length) + { + // The current search pattern corresponds to a valid room id or room alias + searchedRoomIdOrAliasSection = sectionsOffset++; + } + + // The public rooms directory cell is then visible whatever the search activity. + directorySection = sectionsOffset++; + + if (_hideRecents) + { + invitesSection = favoritesSection = conversationSection = lowPrioritySection = -1; + sectionsCount = 0; + } + else + { + if (invitesSection != -1) + { + invitesSection += sectionsOffset; + } + if (favoritesSection != -1) + { + favoritesSection += sectionsOffset; + } + if (conversationSection != -1) + { + conversationSection += sectionsOffset; + } + if (lowPrioritySection != -1) + { + lowPrioritySection += sectionsOffset; + } + } + + sectionsCount += sectionsOffset; + return sectionsCount; + } + return 0; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSUInteger count = 0; + + if (section == searchedRoomIdOrAliasSection) + { + count = 1; + } + else if (section == directorySection) + { + count = 1; + } + else + { + count = [super tableView:tableView numberOfRowsInSection:section]; + } + + return count; +} + +- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame +{ + UIView *sectionHeader = nil; + + if (section != searchedRoomIdOrAliasSection) + { + if (section == directorySection) + { + sectionHeader = [[UIView alloc] initWithFrame:frame]; + sectionHeader.backgroundColor = kRiotColorLightGrey; + + // Add label + frame = sectionHeader.frame; + frame.origin.x = 20; + frame.origin.y = 5; + frame.size.width = sectionHeader.frame.size.width - 10; + frame.size.height -= 10; + UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame]; + headerLabel.font = [UIFont boldSystemFontOfSize:15.0]; + headerLabel.backgroundColor = [UIColor clearColor]; + headerLabel.text = NSLocalizedStringFromTable(@"room_recents_directory", @"Vector", nil); + [sectionHeader addSubview:headerLabel]; + } + else + { + sectionHeader = [super viewForHeaderInSection:section withFrame:frame]; + } + } + + return sectionHeader; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == searchedRoomIdOrAliasSection) + { + RoomIdOrAliasTableViewCell *roomIdOrAliasCell = [tableView dequeueReusableCellWithIdentifier:RoomIdOrAliasTableViewCell.defaultReuseIdentifier]; + if (!roomIdOrAliasCell) + { + roomIdOrAliasCell = [[RoomIdOrAliasTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[RoomIdOrAliasTableViewCell defaultReuseIdentifier]]; + } + + [roomIdOrAliasCell render:roomIdOrAlias]; + + return roomIdOrAliasCell; + } + else if (indexPath.section == directorySection) + { + // For the cell showing the public rooms directory search result, + // skip the MatrixKit mechanism and return directly the UITableViewCell + DirectoryRecentTableViewCell *directoryCell = [tableView dequeueReusableCellWithIdentifier:DirectoryRecentTableViewCell.defaultReuseIdentifier]; + if (!directoryCell) + { + directoryCell = [[DirectoryRecentTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[DirectoryRecentTableViewCell defaultReuseIdentifier]]; + } + + [directoryCell render:self.publicRoomsDirectoryDataSource]; + + return directoryCell; + } + + return [super tableView:tableView cellForRowAtIndexPath:indexPath]; +} + +- (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == searchedRoomIdOrAliasSection) + { + return RoomIdOrAliasTableViewCell.cellHeight; + } + + if (indexPath.section == directorySection) + { + // For the cell showing the public rooms directory search result, + // skip the MatrixKit mechanism and return directly the cell height + return DirectoryRecentTableViewCell.cellHeight; + } + + return [super cellHeightAtIndexPath:indexPath]; +} + +#pragma mark - Override MXKRecentsDataSource + +- (void)searchWithPatterns:(NSArray *)patternsList +{ + // Check whether the typed input is a room alias or a room identifier. + roomIdOrAlias = nil; + if (patternsList.count == 1) + { + NSString *pattern = patternsList[0]; + + if ([MXTools isMatrixRoomAlias:pattern] || [MXTools isMatrixRoomIdentifier:pattern]) + { + // Display this room id/alias only if it is not already joined by the user + MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; + if (![accountManager accountKnowingRoomWithRoomIdOrAlias:pattern]) + { + roomIdOrAlias = pattern; + } + } + } + + [super searchWithPatterns:patternsList]; +} + +@end diff --git a/Riot/ViewController/ContactsTableViewController.h b/Riot/ViewController/ContactsTableViewController.h index ead3c02a9..b12b3cb81 100644 --- a/Riot/ViewController/ContactsTableViewController.h +++ b/Riot/ViewController/ContactsTableViewController.h @@ -77,6 +77,11 @@ */ + (instancetype)contactsTableViewController; +/** + The Google Analytics Instance screen name (Default is "ContactsTable"). + */ +@property (nonatomic) NSString *screenName; + /** The contacts table view. */ @@ -130,5 +135,7 @@ */ @property (nonatomic) id contactsTableViewControllerDelegate; +- (IBAction)onCheckBoxTap:(UITapGestureRecognizer*)sender; + @end diff --git a/Riot/ViewController/ContactsTableViewController.m b/Riot/ViewController/ContactsTableViewController.m index 6bce5ede6..7c98c9eae 100644 --- a/Riot/ViewController/ContactsTableViewController.m +++ b/Riot/ViewController/ContactsTableViewController.m @@ -99,6 +99,8 @@ shrinkedSectionsBitMask = 0; hideNonMatrixEnabledContacts = NO; + + _screenName = @"ContactsTable"; } - (void)viewDidLoad @@ -113,6 +115,8 @@ [[[self class] nib] instantiateWithOwner:self options:nil]; } + [self.tableView registerClass:ContactTableViewCell.class forCellReuseIdentifier:ContactTableViewCell.defaultReuseIdentifier]; + // Hide line separators of empty cells self.tableView.tableFooterView = [[UIView alloc] init]; } @@ -166,7 +170,7 @@ id tracker = [[GAI sharedInstance] defaultTracker]; if (tracker) { - [tracker set:kGAIScreenName value:@"ContactsTable"]; + [tracker set:kGAIScreenName value:_screenName]; [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; } @@ -196,8 +200,7 @@ [[MXKContactManager sharedManager] updateMatrixIDsForAllLocalContacts]; } - // Scroll to the top the current table content if any - [self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:NO]; + [self refreshTableView]; } - (void)viewWillDisappear:(BOOL)animated @@ -548,20 +551,7 @@ } // Prepare a contact cell here - ContactTableViewCell* contactCell = [tableView dequeueReusableCellWithIdentifier:[ContactTableViewCell defaultReuseIdentifier]]; - - if (!contactCell) - { - contactCell = [[ContactTableViewCell alloc] init]; - } - else - { - // Restore default values - contactCell.contentView.alpha = 1; - contactCell.userInteractionEnabled = YES; - contactCell.accessoryType = UITableViewCellAccessoryNone; - contactCell.accessoryView = nil; - } + ContactTableViewCell* contactCell = [tableView dequeueReusableCellWithIdentifier:[ContactTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; MXKContact *contact; diff --git a/Riot/ViewController/FavouritesViewController.h b/Riot/ViewController/FavouritesViewController.h new file mode 100644 index 000000000..b5f570c9e --- /dev/null +++ b/Riot/ViewController/FavouritesViewController.h @@ -0,0 +1,24 @@ +/* + 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 "RecentsViewController.h" + +/** + The `FavouritesViewController` screen is the view controller displayed when `Favourites` tab is selected. + */ +@interface FavouritesViewController : RecentsViewController + +@end diff --git a/Riot/ViewController/FavouritesViewController.m b/Riot/ViewController/FavouritesViewController.m new file mode 100644 index 000000000..f419b4c71 --- /dev/null +++ b/Riot/ViewController/FavouritesViewController.m @@ -0,0 +1,82 @@ +/* + 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 "FavouritesViewController.h" + +#import "AppDelegate.h" + +#import "RecentsDataSource.h" + +@implementation FavouritesViewController + +- (void)finalizeInit +{ + [super finalizeInit]; + + self.screenName = @"Favourites"; + self.delegate = self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.view.accessibilityIdentifier = @"FavouritesVCView"; + self.recentsTableView.accessibilityIdentifier = @"FavouritesVCTableView"; + + [AppDelegate theDelegate].masterTabBarController.navigationItem.title = NSLocalizedStringFromTable(@"title_favourites", @"Vector", nil); +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + if ([self.dataSource isKindOfClass:RecentsDataSource.class]) + { + // Take the lead on the shared data source. + RecentsDataSource *recentsDataSource = (RecentsDataSource*)self.dataSource; + recentsDataSource.areSectionsShrinkable = NO; + [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeFavourites]; + } +} + +- (void)dealloc +{ + +} + +- (void)destroy +{ + [super destroy]; +} + +#pragma mark - UITableView delegate + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + // Hide the unique header + return 0.0f; +} + +#pragma mark - MXKRecentListViewControllerDelegate + +- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectRoom:(NSString *)roomId inMatrixSession:(MXSession *)matrixSession +{ + // Open the room + [[AppDelegate theDelegate].masterTabBarController selectRoomWithId:roomId andEventId:nil inMatrixSession:matrixSession]; +} + +@end diff --git a/Riot/ViewController/HomeViewController.h b/Riot/ViewController/HomeViewController.h index 2086ba074..a37158a96 100644 --- a/Riot/ViewController/HomeViewController.h +++ b/Riot/ViewController/HomeViewController.h @@ -15,13 +15,11 @@ limitations under the License. */ -#import - #import "RecentsViewController.h" /** The `HomeViewController` screen is the main app screen. */ -@interface HomeViewController : RecentsViewController +@interface HomeViewController : RecentsViewController @end diff --git a/Riot/ViewController/HomeViewController.m b/Riot/ViewController/HomeViewController.m index 8c7e3bed2..f55212db4 100644 --- a/Riot/ViewController/HomeViewController.m +++ b/Riot/ViewController/HomeViewController.m @@ -17,41 +17,9 @@ #import "HomeViewController.h" -#import "RecentsDataSource.h" - -#import "RageShakeManager.h" - -//#import "RoomDataSource.h" -//#import "RoomViewController.h" -// -//#import "DirectoryViewController.h" -//#import "ContactDetailsViewController.h" -//#import "SettingsViewController.h" -// -//#import "HomeMessagesSearchViewController.h" -//#import "HomeMessagesSearchDataSource.h" -//#import "HomeFilesSearchViewController.h" -//#import "FilesSearchCellData.h" - #import "AppDelegate.h" -//#import "GBDeviceInfo_iOS.h" - -@interface HomeViewController () -{ - // Display a gradient view above the screen - CAGradientLayer* tableViewMaskLayer; - - // Display a button to a new room - UIImageView* createNewRoomImageView; - - MXHTTPOperation *roomCreationRequest; - - // Current alert (if any). - MXKAlert *currentAlert; -} - -@end +#import "RecentsDataSource.h" @implementation HomeViewController @@ -59,11 +27,7 @@ { [super finalizeInit]; - // Setup `MXKViewControllerHandling` properties - self.defaultBarTintColor = kRiotNavBarTintColor; - self.enableBarTintColorStatusChange = NO; - self.rageShakeManager = [RageShakeManager sharedManager]; - + self.screenName = @"Home"; self.delegate = self; } @@ -71,6 +35,9 @@ { [super viewDidLoad]; + self.view.accessibilityIdentifier = @"HomeVCView"; + self.recentsTableView.accessibilityIdentifier = @"HomeVCTableView"; + [AppDelegate theDelegate].masterTabBarController.navigationItem.title = NSLocalizedStringFromTable(@"title_home", @"Vector", nil); // Add room creation button programatically @@ -80,6 +47,19 @@ // self.searchBar.placeholder = NSLocalizedStringFromTable(@"search_default_placeholder", @"Vector", nil); } +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + if ([self.dataSource isKindOfClass:RecentsDataSource.class]) + { + // Take the lead on the shared data source. + RecentsDataSource *recentsDataSource = (RecentsDataSource*)self.dataSource; + recentsDataSource.areSectionsShrinkable = YES; + [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeHome]; + } +} + - (void)dealloc { @@ -88,145 +68,6 @@ - (void)destroy { [super destroy]; - - if (currentAlert) - { - [currentAlert dismiss:NO]; - currentAlert = nil; - } - - if (roomCreationRequest) - { - [roomCreationRequest cancel]; - roomCreationRequest = nil; - } - - if (createNewRoomImageView) - { - [createNewRoomImageView removeFromSuperview]; - createNewRoomImageView = nil; - tableViewMaskLayer = nil; - } -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - -// // Refresh the search results if a search in in progress -// if (!self.searchBarHidden) -// { -// [self updateSearch]; -// } -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - // Release the current selected item (room/contact...) except if the second view controller is still visible (see splitViewController.isCollapsed condition) - if (!self.splitViewController || self.splitViewController.isCollapsed) - { - [[AppDelegate theDelegate].masterTabBarController releaseSelectedItem]; - } - else - { - // In case of split view controller where the primary and secondary view controllers are displayed side-by-side onscreen, - // the selected room (if any) is highlighted. - [self refreshCurrentSelectedCell:YES]; - } -} - -- (void)viewDidLayoutSubviews -{ - [super viewDidLayoutSubviews]; - - // sanity check - if (tableViewMaskLayer) - { - tableViewMaskLayer.frame = self.recentsTableView.frame; - } -} - -#pragma mark - Internal methods - -- (void)addRoomCreationButton -{ - // Add blur mask programatically - tableViewMaskLayer = [CAGradientLayer layer]; - - CGColorRef opaqueWhiteColor = [UIColor colorWithWhite:1.0 alpha:1.0].CGColor; - CGColorRef transparentWhiteColor = [UIColor colorWithWhite:1.0 alpha:0].CGColor; - - tableViewMaskLayer.colors = [NSArray arrayWithObjects:(__bridge id)transparentWhiteColor, (__bridge id)transparentWhiteColor, (__bridge id)opaqueWhiteColor, nil]; - - // display a gradient to the rencents bottom (20% of the bottom of the screen) - tableViewMaskLayer.locations = [NSArray arrayWithObjects: - [NSNumber numberWithFloat:0], - [NSNumber numberWithFloat:0.85], - [NSNumber numberWithFloat:1.0], nil]; - - tableViewMaskLayer.frame = self.recentsTableView.frame; - tableViewMaskLayer.anchorPoint = CGPointZero; - - // CAConstraint is not supported on IOS. - // it seems only being supported on Mac OS. - // so viewDidLayoutSubviews will refresh the layout bounds. - [self.view.layer addSublayer:tableViewMaskLayer]; - - // Add room create button - createNewRoomImageView = [[UIImageView alloc] init]; - [createNewRoomImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; - [self.view addSubview:createNewRoomImageView]; - - createNewRoomImageView.backgroundColor = [UIColor clearColor]; - createNewRoomImageView.contentMode = UIViewContentModeCenter; - createNewRoomImageView.image = [UIImage imageNamed:@"create_room"]; - - CGFloat side = 78.0f; - NSLayoutConstraint* widthConstraint = [NSLayoutConstraint constraintWithItem:createNewRoomImageView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 - constant:side]; - - NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:createNewRoomImageView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 - constant:side]; - - NSLayoutConstraint* trailingConstraint = [NSLayoutConstraint constraintWithItem:createNewRoomImageView - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:0]; - - NSLayoutConstraint* bottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:createNewRoomImageView - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:9]; - - // Available on iOS 8 and later - [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, trailingConstraint, bottomConstraint]]; - - createNewRoomImageView.userInteractionEnabled = YES; - - // Handle tap gesture - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onNewRoomPressed)]; - [tap setNumberOfTouchesRequired:1]; - [tap setNumberOfTapsRequired:1]; - [tap setDelegate:self]; - [createNewRoomImageView addGestureRecognizer:tap]; } //#pragma mark - Search @@ -401,7 +242,7 @@ #pragma mark - Actions -- (void)onNewRoomPressed +- (void)onRoomCreationButtonPressed { __weak typeof(self) weakSelf = self; @@ -422,7 +263,7 @@ __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf->currentAlert = nil; - [strongSelf createEmptyRoom]; + [strongSelf createAnEmptyRoom]; }]; currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:MXKAlertActionStyleCancel handler:^(MXKAlert *alert) { @@ -436,74 +277,5 @@ currentAlert.mxkAccessibilityIdentifier = @"HomeVCCreateRoomAlert"; [currentAlert showInViewController:self]; } - -- (void)createEmptyRoom -{ - // Sanity check - if (self.mainSession) - { - // Create one room at time - if (!roomCreationRequest) - { - [self startActivityIndicator]; - - // Create an empty room. - roomCreationRequest = [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - success:^(MXRoom *room) { - - roomCreationRequest = nil; - [self stopActivityIndicator]; - if (currentAlert) - { - [currentAlert dismiss:NO]; - currentAlert = nil; - } - - [[AppDelegate theDelegate].masterTabBarController selectRoomWithId:room.state.roomId andEventId:nil inMatrixSession:self.mainSession]; - - // Force the expanded header - [AppDelegate theDelegate].masterTabBarController.currentRoomViewController.showExpandedHeader = YES; - - } failure:^(NSError *error) { - - roomCreationRequest = nil; - [self stopActivityIndicator]; - if (currentAlert) - { - [currentAlert dismiss:NO]; - currentAlert = nil; - } - - NSLog(@"[HomeViewController] Create new room failed"); - - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; - } - else - { - // Ask the user to wait - __weak __typeof(self) weakSelf = self; - currentAlert = [[MXKAlert alloc] initWithTitle:nil - message:NSLocalizedStringFromTable(@"room_creation_wait_for_creation", @"Vector", nil) - style:MXKAlertStyleAlert]; - - currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:MXKAlertActionStyleCancel - handler:^(MXKAlert *alert) { - - __strong __typeof(weakSelf)strongSelf = weakSelf; - strongSelf->currentAlert = nil; - - }]; - currentAlert.mxkAccessibilityIdentifier = @"HomeVCRoomCreationInProgressAlert"; - [currentAlert showInViewController:self]; - } - } -} @end diff --git a/Riot/ViewController/MasterTabBarController.h b/Riot/ViewController/MasterTabBarController.h index 682cbf45c..3b0e0cf4c 100644 --- a/Riot/ViewController/MasterTabBarController.h +++ b/Riot/ViewController/MasterTabBarController.h @@ -19,6 +19,9 @@ #import "AuthenticationViewController.h" #import "HomeViewController.h" +#import "FavouritesViewController.h" +#import "PeopleViewController.h" +#import "RoomsViewController.h" #import "RoomViewController.h" #import "ContactDetailsViewController.h" @@ -85,11 +88,6 @@ */ - (void)releaseSelectedItem; -/** - Remove the current contact details (if any). - */ -- (void)hideContactDetails; - /** Dismiss the unified search screen (if any). */ @@ -100,6 +98,9 @@ @property (nonatomic, readonly) AuthenticationViewController *authViewController; @property (nonatomic, readonly) HomeViewController *homeViewController; +@property (nonatomic, readonly) FavouritesViewController *favouritesViewController; +@property (nonatomic, readonly) PeopleViewController *peopleViewController; +@property (nonatomic, readonly) RoomsViewController *roomsViewController; // References on the currently selected room and its view controller @property (nonatomic, readonly) RoomViewController *currentRoomViewController; diff --git a/Riot/ViewController/MasterTabBarController.m b/Riot/ViewController/MasterTabBarController.m index 2d7bf8be4..b873a8c0a 100644 --- a/Riot/ViewController/MasterTabBarController.m +++ b/Riot/ViewController/MasterTabBarController.m @@ -36,7 +36,8 @@ // The parameters to pass to the Authentification view controller. NSDictionary *authViewControllerRegistrationParameters; - RecentsDataSource *homeDataSource; + // The recents data source shared between all the view controllers of the tab bar. + RecentsDataSource *recentsDataSource; // The current unified search screen if any UnifiedSearchViewController *unifiedSearchViewController; @@ -54,14 +55,19 @@ [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. - // Retrieve the home view controller + // Retrieve the all view controllers _homeViewController = [self.viewControllers objectAtIndex:TABBAR_HOME_INDEX]; + _favouritesViewController = [self.viewControllers objectAtIndex:TABBAR_FAVOURITES_INDEX]; + _peopleViewController = [self.viewControllers objectAtIndex:TABBAR_PEOPLE_INDEX]; + _roomsViewController = [self.viewControllers objectAtIndex:TABBAR_ROOMS_INDEX]; + + // Sanity check + NSAssert(_homeViewController && _favouritesViewController && _peopleViewController && _roomsViewController, @"Something wrong in Main.storyboard"); + + self.tabBar.tintColor = kRiotColorGreen; // Initialize here the data sources if a matrix session has been already set. [self initializeDataSources]; - - // Sanity check - NSAssert(_homeViewController, @"Something wrong in Main.storyboard"); } - (void)viewDidAppear:(BOOL)animated @@ -95,6 +101,9 @@ mxSessionArray = nil; _homeViewController = nil; + _favouritesViewController = nil; + _peopleViewController = nil; + _roomsViewController = nil; if (currentAlert) { @@ -123,8 +132,12 @@ if (mainSession) { // Init the recents data source - homeDataSource = [[RecentsDataSource alloc] initWithMatrixSession:mainSession]; - [_homeViewController displayList:homeDataSource]; + recentsDataSource = [[RecentsDataSource alloc] initWithMatrixSession:mainSession]; + + [_homeViewController displayList:recentsDataSource]; + [_favouritesViewController displayList:recentsDataSource]; + [_peopleViewController displayDirectRooms:recentsDataSource]; + [_roomsViewController displayList:recentsDataSource]; // Check whether there are others sessions NSArray* mxSessions = self.mxSessions; @@ -135,7 +148,7 @@ if (mxSession != mainSession) { // Add the session to the recents data source - [homeDataSource addMatrixSession:mxSession]; + [recentsDataSource addMatrixSession:mxSession]; } } } @@ -148,7 +161,7 @@ if (_homeViewController) { // Check whether the data sources have been initialized. - if (!homeDataSource) + if (!recentsDataSource) { // Add first the session. The updated sessions list will be used during data sources initialization. mxSessionArray = [NSMutableArray array]; @@ -160,8 +173,8 @@ } else { - // Add the session to the existing home data source - [homeDataSource addMatrixSession:mxSession]; + // Add the session to the existing data sources + [recentsDataSource addMatrixSession:mxSession]; } } @@ -174,14 +187,18 @@ - (void)removeMatrixSession:(MXSession *)mxSession { - [homeDataSource removeMatrixSession:mxSession]; + [recentsDataSource removeMatrixSession:mxSession]; // Check whether there are others sessions - if (!homeDataSource.mxSessions.count) + if (!recentsDataSource.mxSessions.count) { [_homeViewController displayList:nil]; - [homeDataSource destroy]; - homeDataSource = nil; + [_favouritesViewController displayList:nil]; + [_peopleViewController displayDirectRooms:nil]; + [_roomsViewController displayList:nil]; + + [recentsDataSource destroy]; + recentsDataSource = nil; } [mxSessionArray removeObject:mxSession]; diff --git a/Riot/ViewController/PeopleViewController.h b/Riot/ViewController/PeopleViewController.h new file mode 100644 index 000000000..e5e31f2ef --- /dev/null +++ b/Riot/ViewController/PeopleViewController.h @@ -0,0 +1,36 @@ +/* + 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 "ContactsTableViewController.h" +#import "RecentsDataSource.h" + +/** + 'PeopleViewController' instance is used to display/filter the direct rooms and a list of contacts. + */ +@interface PeopleViewController : ContactsTableViewController + +/** + Display the direct rooms from the provided data source. + + Note: The provided data source will replace the current data source if any. The caller + should dispose properly this data source if it is not used anymore. + + @param directRoomsDataSource the data source providing the direct rooms list. + */ +- (void)displayDirectRooms:(RecentsDataSource*)directRoomsDataSource; + +@end + diff --git a/Riot/ViewController/PeopleViewController.m b/Riot/ViewController/PeopleViewController.m new file mode 100644 index 000000000..87bbb8e42 --- /dev/null +++ b/Riot/ViewController/PeopleViewController.m @@ -0,0 +1,804 @@ +/* + 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 "PeopleViewController.h" + +#import "UIViewController+RiotSearch.h" + +#import "RageShakeManager.h" + +#import "AppDelegate.h" + +#import "RecentTableViewCell.h" +#import "InviteRecentTableViewCell.h" + +#define CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT 30.0 +#define CONTACTS_TABLEVC_LOCALCONTACTS_SECTION_HEADER_HEIGHT 65.0 + +@interface PeopleViewController () +{ + RecentsDataSource *directRoomsSource; + NSInteger directRoomsSectionNumber; + +// // Search processing +// dispatch_queue_t searchProcessingQueue; +// NSUInteger searchProcessingCount; +// NSString *searchProcessingText; +// NSMutableArray *searchProcessingLocalContacts; +// NSMutableArray *searchProcessingMatrixContacts; +// +// // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. +// id kAppDelegateDidTapStatusBarNotificationObserver; +// +// BOOL forceSearchResultRefresh; +// +// // This dictionary tells for each display name whether it appears several times. +// NSMutableDictionary *isMultiUseNameByDisplayName; +// +// // Shrinked sections. +// NSInteger shrinkedSectionsBitMask; +// + UIView *localContactsCheckboxContainer; + UIImageView *localContactsCheckbox; +} + +@end + +@implementation PeopleViewController + +#pragma mark - Class methods + +//+ (UINib *)nib +//{ +// return [UINib nibWithNibName:NSStringFromClass([PeopleViewController class]) +// bundle:[NSBundle bundleForClass:[PeopleViewController class]]]; +//} +// +//+ (instancetype)contactsTableViewController +//{ +// return [[[self class] alloc] initWithNibName:NSStringFromClass([PeopleViewController class]) +// bundle:[NSBundle bundleForClass:[PeopleViewController class]]]; +//} + +#pragma mark - + +- (void)finalizeInit +{ + [super finalizeInit]; + + directRoomsSectionNumber = 0; + + self.screenName = @"People"; + + self.contactCellAccessoryType = UITableViewCellAccessoryDisclosureIndicator; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + + self.view.accessibilityIdentifier = @"PeopleVCView"; + self.tableView.accessibilityIdentifier = @"PeopleVCTableView"; + + [AppDelegate theDelegate].masterTabBarController.navigationItem.title = NSLocalizedStringFromTable(@"title_people", @"Vector", nil); + + [self.tableView registerClass:RecentTableViewCell.class forCellReuseIdentifier:RecentTableViewCell.defaultReuseIdentifier]; + [self.tableView registerClass:InviteRecentTableViewCell.class forCellReuseIdentifier:InviteRecentTableViewCell.defaultReuseIdentifier]; + + // Hide line separators of empty cells + self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 50)]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)destroy +{ + localContactsCheckboxContainer = nil; + localContactsCheckbox = nil; + + [super destroy]; +} + +- (void)addMatrixSession:(MXSession *)mxSession +{ + [super addMatrixSession:mxSession]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Take the lead on the shared data source. + directRoomsSource.areSectionsShrinkable = NO; + [directRoomsSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModePeople]; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; +} + +#pragma mark - + +- (void)displayDirectRooms:(RecentsDataSource*)directRoomsDataSource; +{ + // Cancel registration on existing dataSource if any + if (directRoomsSource) + { + directRoomsSource.delegate = nil; + + // Remove associated matrix sessions + NSArray *mxSessions = self.mxSessions; + for (MXSession *mxSession in mxSessions) + { + [self removeMatrixSession:mxSession]; + } + } + + directRoomsSource = directRoomsDataSource; + directRoomsSource.delegate = self; + + // Report all matrix sessions at view controller level to update UI according to sessions state + NSArray *mxSessions = directRoomsSource.mxSessions; + for (MXSession *mxSession in mxSessions) + { + [self addMatrixSession:mxSession]; + } + + [self refreshTableView]; +} + +#pragma mark - MXKDataSourceDelegate + +- (Class)cellViewClassForCellData:(MXKCellData*)cellData +{ + id cellDataStoring = (id )cellData; + + if (NSNotFound == [cellDataStoring.recentsDataSource.mxSession.invitedRooms indexOfObject:cellDataStoring.roomSummary.room]) + { + return RecentTableViewCell.class; + } + else + { + return InviteRecentTableViewCell.class; + } +} + +- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData +{ + id cellDataStoring = (id )cellData; + + if (NSNotFound == [cellDataStoring.recentsDataSource.mxSession.invitedRooms indexOfObject:cellDataStoring.roomSummary.room]) + { + return RecentTableViewCell.defaultReuseIdentifier; + } + else + { + return InviteRecentTableViewCell.defaultReuseIdentifier; + } +} + +- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes +{ + if (dataSource == directRoomsSource) + { + // Retrieve the new number of sections related to direct rooms + NSInteger sectionNb = [directRoomsSource numberOfSectionsInTableView:self.tableView]; + + if (directRoomsSectionNumber == sectionNb) + { + // Refresh the sections related to the direct rooms in the table view + NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, directRoomsSectionNumber)]; + [self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationNone]; + } + else if (directRoomsSectionNumber < sectionNb) + { + // Refresh the sections related to the direct rooms in the table view + [self.tableView beginUpdates]; + NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, directRoomsSectionNumber)]; + [self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationNone]; + indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(directRoomsSectionNumber, (sectionNb - directRoomsSectionNumber))]; + [self.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationNone]; + [self.tableView endUpdates]; + } + else + { + // Refresh the sections related to the direct rooms in the table view + [self.tableView beginUpdates]; + NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNb)]; + [self.tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationNone]; + indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(sectionNb, (directRoomsSectionNumber - sectionNb))]; + [self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationNone]; + [self.tableView endUpdates]; + } + + directRoomsSectionNumber = sectionNb; + } +} + +- (void)dataSource:(MXKDataSource *)dataSource didAddMatrixSession:(MXSession *)mxSession +{ + [self addMatrixSession:mxSession]; +} + +- (void)dataSource:(MXKDataSource *)dataSource didRemoveMatrixSession:(MXSession *)mxSession +{ + [self removeMatrixSession:mxSession]; +} + +- (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)actionIdentifier inCell:(id)cell userInfo:(NSDictionary *)userInfo +{ + // Handle here user actions on recents for Riot app + if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellPreviewButtonPressed]) + { + // Retrieve the invited room + MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; + + // Display room preview by selecting it. + [[AppDelegate theDelegate].masterTabBarController selectRoomWithId:invitedRoom.state.roomId andEventId:nil inMatrixSession:invitedRoom.mxSession]; + } + else if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellDeclineButtonPressed]) + { + // Retrieve the invited room + MXRoom *invitedRoom = userInfo[kInviteRecentTableViewCellRoomKey]; + + [self setEditing:NO]; + + // Decline the invitation + [invitedRoom leave:^{ + + [self refreshTableView]; + + } failure:^(NSError *error) { + + NSLog(@"[PeopleViewController] Failed to reject an invited room (%@)", invitedRoom.state.roomId); + + }]; + } +} + +#pragma mark - + +- (void)searchWithPattern:(NSString *)searchText forceReset:(BOOL)forceRefresh complete:(void (^)())complete +{ +// // Update search results. +// searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; +// +// searchProcessingCount++; +// [self startActivityIndicator]; +// +// dispatch_async(searchProcessingQueue, ^{ +// +// if (!searchText.length) +// { +// searchProcessingLocalContacts = nil; +// searchProcessingMatrixContacts = nil; +// +// // Disclose by default the sections if a search was in progress. +// if (searchProcessingText.length) +// { +// shrinkedSectionsBitMask = 0; +// } +// } +// else if (forceRefresh || !searchProcessingText.length || [searchText hasPrefix:searchProcessingText] == NO) +// { +// // Retrieve all the local contacts +// searchProcessingLocalContacts = [self unfilteredLocalContactsArray]; +// +// // Retrieve all known matrix users +// searchProcessingMatrixContacts = [self unfilteredMatrixContactsArray]; +// +// // Disclose the sections +// shrinkedSectionsBitMask = 0; +// } +// +// for (NSUInteger index = 0; index < searchProcessingLocalContacts.count;) +// { +// MXKContact* contact = searchProcessingLocalContacts[index]; +// +// if (![contact hasPrefix:searchText]) +// { +// [searchProcessingLocalContacts removeObjectAtIndex:index]; +// } +// else +// { +// // Next +// index++; +// } +// } +// +// for (NSUInteger index = 0; index < searchProcessingMatrixContacts.count;) +// { +// MXKContact* contact = searchProcessingMatrixContacts[index]; +// +// if (![contact hasPrefix:searchText]) +// { +// [searchProcessingMatrixContacts removeObjectAtIndex:index]; +// } +// else +// { +// // Next +// index++; +// } +// } +// +// // Sort the refreshed list of the invitable contacts +// [[MXKContactManager sharedManager] sortAlphabeticallyContacts:searchProcessingLocalContacts]; +// [[MXKContactManager sharedManager] sortContactsByLastActiveInformation:searchProcessingMatrixContacts]; +// +// searchProcessingText = searchText; +// +// dispatch_sync(dispatch_get_main_queue(), ^{ +// +// // Sanity check: check whether self has been destroyed. +// if (!searchProcessingQueue) +// { +// return; +// } +// +// // Render the search result only if there is no other search in progress. +// searchProcessingCount --; +// +// if (!searchProcessingCount) +// { +// if (!forceSearchResultRefresh) +// { +// [self stopActivityIndicator]; +// +// // Scroll the resulting list to the top only when the search pattern has been modified. +// BOOL shouldScrollToTop = (currentSearchText != searchProcessingText); +// +// // Update the filtered contacts. +// currentSearchText = searchProcessingText; +// filteredLocalContacts = searchProcessingLocalContacts; +// filteredMatrixContacts = searchProcessingMatrixContacts; +// +// if (!self.forceMatrixIdInDisplayName) +// { +// [isMultiUseNameByDisplayName removeAllObjects]; +// for (MXKContact* contact in filteredMatrixContacts) +// { +// isMultiUseNameByDisplayName[contact.displayName] = (isMultiUseNameByDisplayName[contact.displayName] ? @(YES) : @(NO)); +// } +// } +// +// // Refresh display +// [self refreshTableView]; +// +// if (shouldScrollToTop) +// { +// // Scroll to the top +// [self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:NO]; +// } +// +// if (complete) +// { +// complete(); +// } +// } +// else +// { +// // Launch a new search +// forceSearchResultRefresh = NO; +// [self searchWithPattern:searchProcessingText forceReset:YES complete:complete]; +// } +// } +// }); +// +// }); +} + +- (void)refreshTableView +{ + [self.tableView reloadData]; + + // Update the current number of sections related to the direct rooms + directRoomsSectionNumber = [directRoomsSource numberOfSectionsInTableView:self.tableView]; +} + +#pragma mark - UITableView data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + // Retrieve the number of sections related to direct rooms + NSInteger count = [directRoomsSource numberOfSectionsInTableView:self.tableView]; + + // Prepare contacts lists + [super numberOfSectionsInTableView:tableView]; + + // Ignore search input + searchInputSection = -1; + + // Keep visible the header for the contact sections, even if their are empty. + filteredLocalContactsSection = count++; + filteredMatrixContactsSection = count++; + + return count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSInteger count = 0; + + if (section < filteredLocalContactsSection) + { + count = [directRoomsSource tableView:tableView numberOfRowsInSection:section]; + } + else if (section == filteredLocalContactsSection) + { + count = filteredLocalContacts.count; + } + else if (section == filteredMatrixContactsSection) + { + if (currentSearchText.length) + { + count = filteredMatrixContacts.count; + } + else + { + // Display a message to invite the user to use the search field. + count = 1; + } + } + + return count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section < filteredLocalContactsSection) + { + return [directRoomsSource tableView:tableView cellForRowAtIndexPath:indexPath]; + } + + return [super tableView:tableView cellForRowAtIndexPath:indexPath]; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section < filteredLocalContactsSection) + { + return [directRoomsSource tableView:tableView canEditRowAtIndexPath:indexPath]; + } + return NO; +} + +#pragma mark - UITableView delegate + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + if (section == filteredLocalContactsSection) + { + return CONTACTS_TABLEVC_LOCALCONTACTS_SECTION_HEADER_HEIGHT; + } + + return CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section +{ + UIView* sectionHeader; + + CGFloat height = [self tableView:tableView heightForHeaderInSection:section]; + if (height != 0) + { + sectionHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, height)]; + sectionHeader.backgroundColor = kRiotColorLightGrey; + + CGRect frame = sectionHeader.frame; + frame.origin.x = 20; + frame.origin.y = 5; + frame.size.width = sectionHeader.frame.size.width - 10; + frame.size.height = 20; + UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame]; + headerLabel.font = [UIFont boldSystemFontOfSize:15.0]; + headerLabel.backgroundColor = [UIColor clearColor]; + [sectionHeader addSubview:headerLabel]; + + if (section < filteredLocalContactsSection) + { + if (section == (filteredLocalContactsSection - 1)) + { + headerLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"people_conversation_section", @"Vector", nil), [directRoomsSource tableView:self.tableView numberOfRowsInSection:section]]; + } + else + { + headerLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"people_invites_section", @"Vector", nil), [directRoomsSource tableView:self.tableView numberOfRowsInSection:section]]; + } + } + else if (section == filteredLocalContactsSection) + { + headerLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"people_address_book_section", @"Vector", nil), filteredLocalContacts.count]; + } + else if (section == filteredMatrixContactsSection) + { + if (currentSearchText.length) + { + headerLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"people_matrix_users_section", @"Vector", nil), filteredMatrixContacts.count]; + } + else + { + headerLabel.text = NSLocalizedStringFromTable(@"people_matrix_users_default_section", @"Vector", nil); + } + } + + if (section == filteredLocalContactsSection) + { + NSLayoutConstraint *leadingConstraint, *trailingConstraint, *topConstraint, *bottomConstraint; + NSLayoutConstraint *widthConstraint, *heightConstraint, *centerYConstraint; + + if (!localContactsCheckboxContainer) + { + CGFloat containerWidth = sectionHeader.frame.size.width; + + localContactsCheckboxContainer = [[UIView alloc] initWithFrame:CGRectMake(0, CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT, containerWidth, sectionHeader.frame.size.height - CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT)]; + localContactsCheckboxContainer.backgroundColor = [UIColor clearColor]; + localContactsCheckboxContainer.translatesAutoresizingMaskIntoConstraints = NO; + + // Add Checkbox and Label + localContactsCheckbox = [[UIImageView alloc] initWithFrame:CGRectMake(23, 5, 22, 22)]; + localContactsCheckbox.translatesAutoresizingMaskIntoConstraints = NO; + [localContactsCheckboxContainer addSubview:localContactsCheckbox]; + + UILabel *checkboxLabel = [[UILabel alloc] initWithFrame:CGRectMake(54, 5, containerWidth - 64, 30)]; + checkboxLabel.translatesAutoresizingMaskIntoConstraints = NO; + checkboxLabel.textColor = kRiotTextColorBlack; + checkboxLabel.font = [UIFont systemFontOfSize:16.0]; + checkboxLabel.text = NSLocalizedStringFromTable(@"people_address_book_matrix_users_toggle", @"Vector", nil); + [localContactsCheckboxContainer addSubview:checkboxLabel]; + + UIView *checkboxMask = [[UIView alloc] initWithFrame:CGRectMake(16, -2, 36, 36)]; + checkboxMask.translatesAutoresizingMaskIntoConstraints = NO; + [localContactsCheckboxContainer addSubview:checkboxMask]; + // Listen to check box tap + checkboxMask.userInteractionEnabled = YES; + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onCheckBoxTap:)]; + [tapGesture setNumberOfTouchesRequired:1]; + [tapGesture setNumberOfTapsRequired:1]; + [tapGesture setDelegate:self]; + [checkboxMask addGestureRecognizer:tapGesture]; + + // Add switch constraints + leadingConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckbox + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:23]; + + topConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckbox + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeTop + multiplier:1 + constant:5]; + + widthConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckbox + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:22]; + heightConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckbox + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:22]; + + [NSLayoutConstraint activateConstraints:@[leadingConstraint, topConstraint, widthConstraint, heightConstraint]]; + + + // Add Label constraints + centerYConstraint = [NSLayoutConstraint constraintWithItem:checkboxLabel + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckbox + attribute:NSLayoutAttributeCenterY + multiplier:1 + constant:0.0f]; + heightConstraint = [NSLayoutConstraint constraintWithItem:checkboxLabel + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:30]; + leadingConstraint = [NSLayoutConstraint constraintWithItem:checkboxLabel + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckbox + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:10]; + trailingConstraint = [NSLayoutConstraint constraintWithItem:checkboxLabel + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:-10]; + + [NSLayoutConstraint activateConstraints:@[centerYConstraint, heightConstraint, leadingConstraint, trailingConstraint]]; + + // Add check box mask constraints + heightConstraint = [NSLayoutConstraint constraintWithItem:checkboxMask + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:36]; + + centerYConstraint = [NSLayoutConstraint constraintWithItem:checkboxMask + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckbox + attribute:NSLayoutAttributeCenterY + multiplier:1 + constant:0.0f]; + + leadingConstraint = [NSLayoutConstraint constraintWithItem:checkboxMask + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:localContactsCheckbox + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:-7]; + + trailingConstraint = [NSLayoutConstraint constraintWithItem:checkboxMask + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:checkboxLabel + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:0]; + + [NSLayoutConstraint activateConstraints:@[heightConstraint, centerYConstraint, leadingConstraint, trailingConstraint]]; + } + + // Set the right value of the tick box + localContactsCheckbox.image = hideNonMatrixEnabledContacts ? [UIImage imageNamed:@"selection_tick"] : [UIImage imageNamed:@"selection_untick"]; + + // Add the check box container + [sectionHeader addSubview:localContactsCheckboxContainer]; + leadingConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:sectionHeader + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:0]; + widthConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:sectionHeader + attribute:NSLayoutAttributeWidth + multiplier:1 + constant:0]; + topConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:sectionHeader + attribute:NSLayoutAttributeTop + multiplier:1 + constant:CONTACTS_TABLEVC_DEFAULT_SECTION_HEADER_HEIGHT]; + bottomConstraint = [NSLayoutConstraint constraintWithItem:localContactsCheckboxContainer + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:sectionHeader + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:0]; + + [NSLayoutConstraint activateConstraints:@[leadingConstraint, widthConstraint, topConstraint, bottomConstraint]]; + } + } + return sectionHeader; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section < filteredLocalContactsSection) + { + return [directRoomsSource cellHeightAtIndexPath:indexPath]; + } + + return 74.0; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section < filteredLocalContactsSection) + { + // Open the room + id cellData = [directRoomsSource cellDataAtIndexPath:indexPath]; + + [[AppDelegate theDelegate].masterTabBarController selectRoomWithId:cellData.roomSummary.roomId andEventId:nil inMatrixSession:cellData.roomSummary.room.mxSession]; + } + else + { + NSInteger row = indexPath.row; + MXKContact *mxkContact; + + if (indexPath.section == filteredLocalContactsSection) + { + mxkContact = filteredLocalContacts[row]; + } + else if (indexPath.section == filteredMatrixContactsSection) + { + mxkContact = filteredMatrixContacts[row]; + } + + if (mxkContact) + { + [[AppDelegate theDelegate].masterTabBarController selectContact:mxkContact]; + } + } + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - UISearchBar delegate + +//- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText +//{ +// [self searchWithPattern:searchText forceReset:NO complete:nil]; +//} +// +//- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar +//{ +// // "Done" key has been pressed. +// +// // Check whether the current search input is a valid email or a Matrix user ID +// if (currentSearchText.length && ([MXTools isEmailAddress:currentSearchText] || [MXTools isMatrixUserIdentifier:currentSearchText])) +// { +// // Select the contact related to the search input, rather than having to hit + +// if (searchInputSection != -1) +// { +// [self tableView:self.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:searchInputSection]]; +// return; +// } +// +// } +// +// // Dismiss keyboard +// [searchBar resignFirstResponder]; +//} +// +//- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar +//{ +// searchBar.text = nil; +// +// // Reset filtering +// [self searchWithPattern:nil forceReset:NO complete:nil]; +// +// // Leave search +// [searchBar resignFirstResponder]; +// +// [self withdrawViewControllerAnimated:YES completion:nil]; +//} + +@end diff --git a/Riot/ViewController/RecentsViewController.h b/Riot/ViewController/RecentsViewController.h index 442672f63..85616a7de 100644 --- a/Riot/ViewController/RecentsViewController.h +++ b/Riot/ViewController/RecentsViewController.h @@ -16,7 +16,19 @@ #import -@interface RecentsViewController : MXKRecentListViewController +@interface RecentsViewController : MXKRecentListViewController +{ +@protected + /** + The image view of the room creation button. + */ + UIImageView* createNewRoomImageView; + + /** + Current alert (if any). + */ + MXKAlert *currentAlert; +} /** If YES, the table view will scroll at the top on the next data source refresh. @@ -24,6 +36,11 @@ */ @property (nonatomic) BOOL shouldScrollToTopOnRefresh; +/** + The Google Analytics Instance screen name (Default is "RecentsScreen"). + */ +@property (nonatomic) NSString *screenName; + /** Refresh the cell selection in the table. @@ -33,5 +50,23 @@ */ - (void)refreshCurrentSelectedCell:(BOOL)forceVisible; + +#pragma mark - Room creation +/** + Add a Room creation button at the right bottom corner of the view. + */ +- (void)addRoomCreationButton; + +/** + Action triggered when the user taps on the Room creation button. + Create an empty room by default. + */ +- (void)onRoomCreationButtonPressed; + +/** + Create an empty room. + */ +- (void)createAnEmptyRoom; + @end diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index fd7e34bc4..a67b8d0b8 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -60,6 +60,11 @@ // Observe kMXNotificationCenterDidUpdateRules to update missed messages counts. id kMXNotificationCenterDidUpdateRulesObserver; + + // The gradient view displayed above the screen + CAGradientLayer* tableViewMaskLayer; + + MXHTTPOperation *roomCreationRequest; } @end @@ -84,6 +89,9 @@ self.defaultBarTintColor = kRiotNavBarTintColor; self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; + + // Set default screen name + _screenName = @"RecentsScreen"; } - (void)viewDidLoad @@ -118,6 +126,12 @@ { [super destroy]; + if (currentAlert) + { + [currentAlert dismiss:NO]; + currentAlert = nil; + } + if (UIApplicationDidEnterBackgroundNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:UIApplicationDidEnterBackgroundNotificationObserver]; @@ -146,7 +160,7 @@ id tracker = [[GAI sharedInstance] defaultTracker]; if (tracker) { - [tracker set:kGAIScreenName value:@"UnifiedSearchRooms"]; + [tracker set:kGAIScreenName value:_screenName]; [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; } @@ -219,6 +233,17 @@ [super viewDidDisappear:animated]; } +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + + // sanity check + if (tableViewMaskLayer) + { + tableViewMaskLayer.frame = self.recentsTableView.frame; + } +} + #pragma mark - - (void)refreshCurrentSelectedCell:(BOOL)forceVisible @@ -353,7 +378,7 @@ - (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)actionIdentifier inCell:(id)cell userInfo:(NSDictionary *)userInfo { - // Handle here user actions on recents for Vector app + // Handle here user actions on recents for Riot app if ([actionIdentifier isEqualToString:kInviteRecentTableViewCellPreviewButtonPressed]) { // Retrieve the invited room @@ -687,7 +712,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - return [(RecentsDataSource*)self.dataSource heightForHeaderInSection:section]; + return 30.0f; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath @@ -930,5 +955,159 @@ } } +#pragma mark - Room creation + +- (void)addRoomCreationButton +{ + // Add blur mask programatically + tableViewMaskLayer = [CAGradientLayer layer]; + + CGColorRef opaqueWhiteColor = [UIColor colorWithWhite:1.0 alpha:1.0].CGColor; + CGColorRef transparentWhiteColor = [UIColor colorWithWhite:1.0 alpha:0].CGColor; + + tableViewMaskLayer.colors = [NSArray arrayWithObjects:(__bridge id)transparentWhiteColor, (__bridge id)transparentWhiteColor, (__bridge id)opaqueWhiteColor, nil]; + + // display a gradient to the rencents bottom (20% of the bottom of the screen) + tableViewMaskLayer.locations = [NSArray arrayWithObjects: + [NSNumber numberWithFloat:0], + [NSNumber numberWithFloat:0.85], + [NSNumber numberWithFloat:1.0], nil]; + + tableViewMaskLayer.frame = self.recentsTableView.frame; + tableViewMaskLayer.anchorPoint = CGPointZero; + + // CAConstraint is not supported on IOS. + // it seems only being supported on Mac OS. + // so viewDidLayoutSubviews will refresh the layout bounds. + [self.view.layer addSublayer:tableViewMaskLayer]; + + // Add room create button + createNewRoomImageView = [[UIImageView alloc] init]; + [createNewRoomImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self.view addSubview:createNewRoomImageView]; + + createNewRoomImageView.backgroundColor = [UIColor clearColor]; + createNewRoomImageView.contentMode = UIViewContentModeCenter; + createNewRoomImageView.image = [UIImage imageNamed:@"create_room"]; + + CGFloat side = 78.0f; + NSLayoutConstraint* widthConstraint = [NSLayoutConstraint constraintWithItem:createNewRoomImageView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:side]; + + NSLayoutConstraint* heightConstraint = [NSLayoutConstraint constraintWithItem:createNewRoomImageView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1 + constant:side]; + + NSLayoutConstraint* trailingConstraint = [NSLayoutConstraint constraintWithItem:createNewRoomImageView + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:0]; + + NSLayoutConstraint* bottomConstraint = [NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:createNewRoomImageView + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:9]; + + // Available on iOS 8 and later + [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, trailingConstraint, bottomConstraint]]; + + createNewRoomImageView.userInteractionEnabled = YES; + + // Handle tap gesture + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onRoomCreationButtonPressed)]; + [tap setNumberOfTouchesRequired:1]; + [tap setNumberOfTapsRequired:1]; + [tap setDelegate:self]; + [createNewRoomImageView addGestureRecognizer:tap]; +} + +- (void)onRoomCreationButtonPressed +{ + [self createAnEmptyRoom]; +} + +- (void)createAnEmptyRoom +{ + // Sanity check + if (self.mainSession) + { + // Create one room at time + if (!roomCreationRequest) + { + [self startActivityIndicator]; + + // Create an empty room. + roomCreationRequest = [self.mainSession createRoom:nil + visibility:kMXRoomDirectoryVisibilityPrivate + roomAlias:nil + topic:nil + success:^(MXRoom *room) { + + roomCreationRequest = nil; + [self stopActivityIndicator]; + if (currentAlert) + { + [currentAlert dismiss:NO]; + currentAlert = nil; + } + + [[AppDelegate theDelegate].masterTabBarController selectRoomWithId:room.state.roomId andEventId:nil inMatrixSession:self.mainSession]; + + // Force the expanded header + [AppDelegate theDelegate].masterTabBarController.currentRoomViewController.showExpandedHeader = YES; + + } failure:^(NSError *error) { + + roomCreationRequest = nil; + [self stopActivityIndicator]; + if (currentAlert) + { + [currentAlert dismiss:NO]; + currentAlert = nil; + } + + NSLog(@"[RecentsViewController] Create new room failed"); + + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + else + { + // Ask the user to wait + __weak __typeof(self) weakSelf = self; + currentAlert = [[MXKAlert alloc] initWithTitle:nil + message:NSLocalizedStringFromTable(@"room_creation_wait_for_creation", @"Vector", nil) + style:MXKAlertStyleAlert]; + + currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:MXKAlertActionStyleCancel + handler:^(MXKAlert *alert) { + + __strong __typeof(weakSelf)strongSelf = weakSelf; + strongSelf->currentAlert = nil; + + }]; + currentAlert.mxkAccessibilityIdentifier = @"RecentsVCRoomCreationInProgressAlert"; + [currentAlert showInViewController:self]; + } + } +} @end diff --git a/Riot/ViewController/RoomsViewController.h b/Riot/ViewController/RoomsViewController.h new file mode 100644 index 000000000..45c77c6f2 --- /dev/null +++ b/Riot/ViewController/RoomsViewController.h @@ -0,0 +1,24 @@ +/* + 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 "RecentsViewController.h" + +/** + The `RoomsViewController` screen is the view controller displayed when `Rooms` tab is selected. + */ +@interface RoomsViewController : RecentsViewController + +@end diff --git a/Riot/ViewController/RoomsViewController.m b/Riot/ViewController/RoomsViewController.m new file mode 100644 index 000000000..f7f992eca --- /dev/null +++ b/Riot/ViewController/RoomsViewController.m @@ -0,0 +1,78 @@ +/* + 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 "RoomsViewController.h" + +#import "AppDelegate.h" + +#import "RecentsDataSource.h" + +@implementation RoomsViewController + +- (void)finalizeInit +{ + [super finalizeInit]; + + self.screenName = @"Rooms"; + self.delegate = self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.view.accessibilityIdentifier = @"RoomsVCView"; + self.recentsTableView.accessibilityIdentifier = @"RoomsVCTableView"; + + [AppDelegate theDelegate].masterTabBarController.navigationItem.title = NSLocalizedStringFromTable(@"title_rooms", @"Vector", nil); + + // Add room creation button programatically + [self addRoomCreationButton]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + if ([self.dataSource isKindOfClass:RecentsDataSource.class]) + { + // Take the lead on the shared data source. + RecentsDataSource *recentsDataSource = (RecentsDataSource*)self.dataSource; + recentsDataSource.areSectionsShrinkable = NO; + [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeRooms]; + } +} + +- (void)dealloc +{ + +} + +- (void)destroy +{ + [super destroy]; +} + + +#pragma mark - MXKRecentListViewControllerDelegate + +- (void)recentListViewController:(MXKRecentListViewController *)recentListViewController didSelectRoom:(NSString *)roomId inMatrixSession:(MXSession *)matrixSession +{ + // Open the room + [[AppDelegate theDelegate].masterTabBarController selectRoomWithId:roomId andEventId:nil inMatrixSession:matrixSession]; +} + +@end diff --git a/Riot/ViewController/UnifiedSearchViewController.m b/Riot/ViewController/UnifiedSearchViewController.m index 4a9c8f561..28de94549 100644 --- a/Riot/ViewController/UnifiedSearchViewController.m +++ b/Riot/ViewController/UnifiedSearchViewController.m @@ -16,7 +16,7 @@ #import "UnifiedSearchViewController.h" -#import "RecentsDataSource.h" +#import "UnifiedSearchRecentsDataSource.h" #import "RecentsViewController.h" #import "RoomDataSource.h" @@ -38,7 +38,7 @@ @interface UnifiedSearchViewController () { RecentsViewController *recentsViewController; - RecentsDataSource *recentsDataSource; + UnifiedSearchRecentsDataSource *recentsDataSource; HomeMessagesSearchViewController *messagesSearchViewController; HomeMessagesSearchDataSource *messagesSearchDataSource; @@ -71,6 +71,7 @@ [titles addObject: NSLocalizedStringFromTable(@"search_rooms", @"Vector", nil)]; recentsViewController = [RecentsViewController recentListViewController]; + recentsViewController.screenName = @"UnifiedSearchRooms"; recentsViewController.delegate = self; [viewControllers addObject:recentsViewController]; @@ -193,8 +194,7 @@ if (mainSession) { // Init the recents data source - recentsDataSource = [[RecentsDataSource alloc] initWithMatrixSession:mainSession]; - recentsDataSource.hidePublicRoomsDirectory = NO; + recentsDataSource = [[UnifiedSearchRecentsDataSource alloc] initWithMatrixSession:mainSession]; [recentsViewController displayList:recentsDataSource]; // Init the search for messages diff --git a/Riot/Views/Contact/ContactTableViewCell.m b/Riot/Views/Contact/ContactTableViewCell.m index 04c01c6ec..b3ff752ab 100644 --- a/Riot/Views/Contact/ContactTableViewCell.m +++ b/Riot/Views/Contact/ContactTableViewCell.m @@ -60,6 +60,17 @@ self.thumbnailView.clipsToBounds = YES; } +- (void)prepareForReuse +{ + [super prepareForReuse]; + + // Restore default values + self.contentView.alpha = 1; + self.userInteractionEnabled = YES; + self.accessoryType = UITableViewCellAccessoryNone; + self.accessoryView = nil; +} + - (void)setShowCustomAccessoryView:(BOOL)show { _showCustomAccessoryView = show;