diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h index 8cc599eba..cf6329d0f 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h @@ -133,10 +133,15 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange; - (void)forceRefresh; /** - Tell whether the sections are shrinkable. NO by default. + Tell whether the sections are shrinkable. YES by default. */ @property (nonatomic) BOOL areSectionsShrinkable; +/** + Return true if the given section is currently shrinked. + */ +- (BOOL)isSectionShrinkedAt:(NSInteger)section; + /** Get the sticky header view for the specified section. diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index faaa6554b..0430b3304 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -78,7 +78,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [self resetSectionIndexes]; - _areSectionsShrinkable = NO; + _areSectionsShrinkable = YES; shrinkedSectionsBitMask = 0; roomTagsListenerByUserId = [[NSMutableDictionary alloc] init]; @@ -660,7 +660,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou title = [VectorL10n roomRecentsSuggestedRoomsSection]; } - if (count) + if (count && !(section == invitesSection)) { NSString *roomCount = [NSString stringWithFormat:@" %tu", count]; @@ -683,12 +683,16 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return sectionTitle; } -- (UIView *)badgeViewForHeaderTitleInHomeSection:(NSInteger)section +- (UIView *)badgeViewForHeaderTitleInSection:(NSInteger)section { // Prepare a badge to display the total of missed notifications in this section. id counts = nil; UIView *missedNotifAndUnreadBadgeBgView = nil; - + + if (section == invitesSection) + { + counts = self.recentsListService.invitedRoomListData.counts; + } if (section == favoritesSection) { counts = self.recentsListService.favoritedRoomListData.counts; @@ -714,8 +718,9 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou counts = self.recentsListService.suggestedRoomListData.counts; } - NSUInteger numberOfNotifications = counts.total.numberOfNotifications; - NSUInteger numberOfHighlights = counts.total.numberOfHighlights; + // Invites are counted as highlights for the badge view display. + NSUInteger numberOfNotifications = counts.total.numberOfNotifications + counts.total.numberOfInvitedRooms; + NSUInteger numberOfHighlights = counts.total.numberOfHighlights + counts.total.numberOfInvitedRooms; if (numberOfNotifications) { @@ -832,14 +837,16 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou chevronView.contentMode = UIViewContentModeCenter; sectionHeader.accessoryView = chevronView; } - else if (_recentsDataSourceMode == RecentsDataSourceModeHome) + if (_recentsDataSourceMode == RecentsDataSourceModeHome + || _recentsDataSourceMode == RecentsDataSourceModePeople + || _recentsDataSourceMode == RecentsDataSourceModeRooms) { // Add a badge to display the total of missed notifications by section. - UIView *badgeView = [self badgeViewForHeaderTitleInHomeSection:section]; + UIView *badgeView = [self badgeViewForHeaderTitleInSection:section]; if (badgeView) { - sectionHeader.accessoryView = badgeView; + sectionHeader.rightAccessoryView = badgeView; } } @@ -1509,4 +1516,55 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [self.delegate dataSource:self didCellChange:update]; } +#pragma mark - Shrinkable +- (BOOL)isSectionShrinkedAt:(NSInteger)section +{ + if (_areSectionsShrinkable == NO) + { + return NO; + } + + if (section == favoritesSection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_FAVORITES)) + { + return YES; + } + + if (section == peopleSection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_PEOPLE)) + { + return YES; + } + + if (section == conversationSection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_CONVERSATIONS)) + { + return YES; + } + + if (section == directorySection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_DIRECTORY)) + { + return YES; + } + + if (section == lowPrioritySection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_LOWPRIORITY)) + { + return YES; + } + + if (section == serverNoticeSection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_SERVERNOTICE)) + { + return YES; + } + + if (section == invitesSection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_INVITES)) + { + return YES; + } + + if (section == suggestedRoomsSection && (shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_SUGGESTED)) + { + return YES; + } + + return NO; +} + @end diff --git a/Riot/Modules/Common/Recents/Model/DiscussionsCount.swift b/Riot/Modules/Common/Recents/Model/DiscussionsCount.swift index ee80cc52f..dad358771 100644 --- a/Riot/Modules/Common/Recents/Model/DiscussionsCount.swift +++ b/Riot/Modules/Common/Recents/Model/DiscussionsCount.swift @@ -51,10 +51,10 @@ public class DiscussionsCount: NSObject { super.init() } - public init(withRoomListDataCounts counts: MXRoomListDataCounts) { - self.numberOfNotified = counts.numberOfNotifiedRooms - self.numberOfHighlighted = counts.numberOfHighlightedRooms + counts.numberOfInvitedRooms - self.numberOfUnsent = counts.numberOfUnsentRooms + public init(withRoomListDataCounts counts: [MXRoomListDataCounts]) { + self.numberOfNotified = counts.reduce(0, { $0 + $1.numberOfNotifiedRooms }) + self.numberOfHighlighted = counts.reduce(0, { $0 + $1.numberOfHighlightedRooms + $1.numberOfInvitedRooms }) + self.numberOfUnsent = counts.reduce(0, { $0 + $1.numberOfUnsentRooms }) super.init() } } diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 10ef43a4c..2f9893d32 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -1042,6 +1042,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro SectionHeaderView *updatedSectionHeaderView = (SectionHeaderView *)updatedHeaderView; sectionHeaderView.headerLabel = updatedSectionHeaderView.headerLabel; sectionHeaderView.accessoryView = updatedSectionHeaderView.accessoryView; + sectionHeaderView.rightAccessoryView = updatedSectionHeaderView.rightAccessoryView; } } } diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index c9eb94209..f37ad11ae 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -32,7 +32,18 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { // MARK: - Fetchers - private var invitedRoomListDataFetcher: MXRoomListDataFetcher? + private var invitedRoomListDataFetcher: MXRoomListDataFetcher? { + switch mode { + case .home: + return invitedRoomListDataFetcherForHome + case .people: + return invitedRoomListDataFetcherForPeople + case .rooms: + return invitedRoomListDataFetcherForRooms + default: + return nil + } + } private var favoritedRoomListDataFetcher: MXRoomListDataFetcher? private var directRoomListDataFetcher: MXRoomListDataFetcher? { switch mode { @@ -62,19 +73,24 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { private var conversationRoomListDataFetcherForRooms: MXRoomListDataFetcher? private var directRoomListDataFetcherForHome: MXRoomListDataFetcher? private var directRoomListDataFetcherForPeople: MXRoomListDataFetcher? - + private var invitedRoomListDataFetcherForHome: MXRoomListDataFetcher? + private var invitedRoomListDataFetcherForPeople: MXRoomListDataFetcher? + private var invitedRoomListDataFetcherForRooms: MXRoomListDataFetcher? + // MARK: - Private private var fetcherTypesForMode: [RecentsDataSourceMode: FetcherTypes] = [ .home: [.invited, .favorited, .directHome, .conversationHome, .lowPriority, .serverNotice, .suggested], .favourites: [.favorited], - .people: [.directPeople], - .rooms: [.conversationRooms, .suggested] + .people: [.invited, .directPeople], + .rooms: [.invited, .conversationRooms, .suggested] ] private var allFetchers: [MXRoomListDataFetcher] { return [ - invitedRoomListDataFetcher, + invitedRoomListDataFetcherForHome, + invitedRoomListDataFetcherForPeople, + invitedRoomListDataFetcherForRooms, favoritedRoomListDataFetcher, directRoomListDataFetcherForHome, directRoomListDataFetcherForPeople, @@ -120,7 +136,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { } return result } - + private var hideInvitedSection: Bool { return MXSDKOptions.sharedInstance().autoAcceptRoomInvites } @@ -204,20 +220,20 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { guard let totalCounts = favoritedRoomListDataFetcher?.data?.counts.total else { return .zero } - return DiscussionsCount(withRoomListDataCounts: totalCounts) + return DiscussionsCount(withRoomListDataCounts: [totalCounts]) } public var peopleMissedDiscussionsCount: DiscussionsCount { - guard let totalCounts = directRoomListDataFetcherForPeople?.data?.counts.total else { - return .zero - } + let invitesCount = invitedRoomListDataFetcherForPeople?.data?.counts.total + let directCount = directRoomListDataFetcherForPeople?.data?.counts.total + let totalCounts = [invitesCount, directCount].compactMap { $0 } return DiscussionsCount(withRoomListDataCounts: totalCounts) } public var conversationMissedDiscussionsCount: DiscussionsCount { - guard let totalCounts = conversationRoomListDataFetcherForRooms?.data?.counts.total else { - return .zero - } + let invitesCount = invitedRoomListDataFetcherForRooms?.data?.counts.total + let conversationCount = conversationRoomListDataFetcherForRooms?.data?.counts.total + let totalCounts = [invitesCount, conversationCount].compactMap { $0 } return DiscussionsCount(withRoomListDataCounts: totalCounts) } @@ -269,7 +285,9 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { removeAllDelegates() allFetchers.forEach({ $0.stop() }) - invitedRoomListDataFetcher = nil + invitedRoomListDataFetcherForHome = nil + invitedRoomListDataFetcherForPeople = nil + invitedRoomListDataFetcherForRooms = nil favoritedRoomListDataFetcher = nil directRoomListDataFetcherForHome = nil directRoomListDataFetcherForPeople = nil @@ -435,7 +453,8 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { private func createCommonRoomListDataFetcher(withDataTypes dataTypes: MXRoomSummaryDataTypes = [], onlySuggested: Bool = false, - paginate: Bool = true) -> MXRoomListDataFetcher { + paginate: Bool = true, + strictMatches: Bool = false) -> MXRoomListDataFetcher { guard let session = session else { fatalError("Session deallocated") } @@ -443,7 +462,8 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { onlySuggested: onlySuggested, query: query, space: space, - showAllRoomsInHomeSpace: showAllRoomsInHomeSpace) + showAllRoomsInHomeSpace: showAllRoomsInHomeSpace, + strictMatches: strictMatches) let fetchOptions = MXRoomListDataFetchOptions(filterOptions: filterOptions, sortOptions: sortOptions, @@ -455,6 +475,22 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { } return fetcher } + + private func createInvitedRoomListDataFetcherForPeople() -> MXRoomListDataFetcher { + let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.invited, .direct], paginate: false, strictMatches: true) + updateInvitedFetcher(fetcher, for: .people) + fetcher.addDelegate(self) + fetcher.paginate() + return fetcher + } + + private func createInvitedRoomListDataFetcherForRooms() -> MXRoomListDataFetcher { + let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.invited], paginate: false) + updateInvitedFetcher(fetcher, for: .rooms) + fetcher.addDelegate(self) + fetcher.paginate() + return fetcher + } private func createDirectRoomListDataFetcherForHome() -> MXRoomListDataFetcher { let fetcher = createCommonRoomListDataFetcher(withDataTypes: [.direct], paginate: false) @@ -500,7 +536,9 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { return } if !hideInvitedSection { - invitedRoomListDataFetcher = createCommonRoomListDataFetcher(withDataTypes: [.invited]) + invitedRoomListDataFetcherForHome = createCommonRoomListDataFetcher(withDataTypes: [.invited]) + invitedRoomListDataFetcherForPeople = createInvitedRoomListDataFetcherForPeople() + invitedRoomListDataFetcherForRooms = createInvitedRoomListDataFetcherForRooms() } favoritedRoomListDataFetcher = createCommonRoomListDataFetcher(withDataTypes: [.favorited]) directRoomListDataFetcherForHome = createDirectRoomListDataFetcherForHome() @@ -516,18 +554,30 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { } private func updateDirectFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) { - var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .space] + var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .space, .invited, .lowPriority] switch mode { case .home: - notDataTypes.insert([.invited, .favorited, .lowPriority]) + notDataTypes.insert(.favorited) fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes case .people: - notDataTypes.insert([.lowPriority]) fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes default: break } } + + private func updateInvitedFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) { + var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .lowPriority, .serverNotice, .space] + switch mode { + case .people: + fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes + case .rooms: + notDataTypes.insert([.direct]) + fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes + default: + break + } + } private func updateFavoritedFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) { switch mode { @@ -543,15 +593,12 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { } private func updateConversationFetcher(_ fetcher: MXRoomListDataFetcher, for mode: RecentsDataSourceMode) { - var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .direct, .lowPriority, .serverNotice, .space] + var notDataTypes: MXRoomSummaryDataTypes = [.hidden, .conferenceUser, .direct, .invited, .lowPriority, .serverNotice, .space] switch mode { case .home: - notDataTypes.insert([.invited, .favorited]) + notDataTypes.insert(.favorited) fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes case .rooms: - if hideInvitedSection { - notDataTypes.insert(.invited) - } fetcher.fetchOptions.filterOptions.notDataTypes = notDataTypes default: break @@ -603,7 +650,7 @@ private struct FetcherTypes: OptionSet { static let lowPriority = FetcherTypes(rawValue: 1 << 6) static let serverNotice = FetcherTypes(rawValue: 1 << 7) static let suggested = FetcherTypes(rawValue: 1 << 8) - + static let none: FetcherTypes = [] static let all: FetcherTypes = [ .invited, .favorited, .directHome, .directPeople, .conversationHome, .conversationRooms, .lowPriority, .serverNotice, .suggested] diff --git a/Riot/Modules/Common/SectionHeaders/SectionHeaderView.h b/Riot/Modules/Common/SectionHeaders/SectionHeaderView.h index 515ee86a4..b602f6c6c 100644 --- a/Riot/Modules/Common/SectionHeaders/SectionHeaderView.h +++ b/Riot/Modules/Common/SectionHeaders/SectionHeaderView.h @@ -52,6 +52,11 @@ */ @property (nonatomic, strong) UIView *accessoryView; +/** + Right accessory view for header. Both width and height will be used. + */ +@property (nonatomic, strong) UIView *rightAccessoryView; + /** A view which spans the bottom view. No frame value will be used. Height will be remaining of the view at below topViewHeight. */ diff --git a/Riot/Modules/Common/SectionHeaders/SectionHeaderView.m b/Riot/Modules/Common/SectionHeaders/SectionHeaderView.m index 54150ac8e..9c039516a 100644 --- a/Riot/Modules/Common/SectionHeaders/SectionHeaderView.m +++ b/Riot/Modules/Common/SectionHeaders/SectionHeaderView.m @@ -85,6 +85,19 @@ static const CGFloat kInterItemsSpaceHorizontal = 8.0; [self setNeedsLayout]; } +- (void)setRightAccessoryView:(UIView *)rightAccessoryView +{ + // remove old one + [_rightAccessoryView removeFromSuperview]; + _rightAccessoryView = rightAccessoryView; + if (_rightAccessoryView) + { + // add new one + [self.contentView addSubview:_rightAccessoryView]; + } + [self setNeedsLayout]; +} + - (void)setBottomView:(UIView *)bottomView { // remove old one @@ -169,6 +182,10 @@ static const CGFloat kInterItemsSpaceHorizontal = 8.0; { rightMargin += _accessoryView.frame.size.width + kInterItemsSpaceHorizontal; } + if (_rightAccessoryView) + { + rightMargin += _rightAccessoryView.frame.size.width + kInterItemsSpaceHorizontal; + } if (_bottomView) { // set header label top @@ -179,7 +196,8 @@ static const CGFloat kInterItemsSpaceHorizontal = 8.0; // center header label vertically frame.origin.y = MAX(0, (self.contentView.bounds.size.height - frame.size.height)/2); } - frame.size.width = self.contentView.bounds.size.width - leftMargin - rightMargin; + frame.size.width = MIN(self.contentView.bounds.size.width - leftMargin - rightMargin, + [_headerLabel sizeThatFits:self.frame.size].width); _headerLabel.frame = frame; } @@ -190,11 +208,30 @@ static const CGFloat kInterItemsSpaceHorizontal = 8.0; rightMargin = MAX(_rightInset, 20); CGRect frame = _accessoryView.frame; - frame.origin.x = self.contentView.bounds.size.width - frame.size.width - rightMargin; + if(_headerLabel) + { + frame.origin.x = leftMargin + _headerLabel.frame.size.width + kInterItemsSpaceHorizontal; + } + else + { + frame.origin.x = leftMargin; + } frame.origin.y = MAX(0, (_topViewHeight - frame.size.height)/2); _accessoryView.frame = frame; } + if (_rightAccessoryView) + { + // reset margins + leftMargin = MAX(_leftInset, 20); + rightMargin = MAX(_rightInset, 20); + + CGRect frame = _rightAccessoryView.frame; + frame.origin.x = self.contentView.bounds.size.width - frame.size.width - rightMargin; + frame.origin.y = MAX(0, (_topViewHeight - frame.size.height)/2); + _rightAccessoryView.frame = frame; + } + if (_bottomView) { // reset margins diff --git a/Riot/Modules/Favorites/FavouritesViewController.m b/Riot/Modules/Favorites/FavouritesViewController.m index da680c7ee..2ff0b52f8 100644 --- a/Riot/Modules/Favorites/FavouritesViewController.m +++ b/Riot/Modules/Favorites/FavouritesViewController.m @@ -67,7 +67,6 @@ if (recentsDataSource) { // Take the lead on the shared data source. - recentsDataSource.areSectionsShrinkable = NO; [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeFavourites]; } } diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index 95a489e3b..c5ce33e6b 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -111,7 +111,6 @@ if (recentsDataSource) { // Take the lead on the shared data source. - recentsDataSource.areSectionsShrinkable = NO; [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeHome]; } @@ -371,9 +370,16 @@ { // Edit the potential selected room (see `onCollectionViewCellLongPress`). editedRoomId = selectedRoomId; - - // Each rooms section is represented by only one collection view. - return 1; + + if ([recentsDataSource isSectionShrinkedAt:section]) + { + return 0; + } + else + { + // Each rooms section is represented by only one collection view. + return 1; + } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath diff --git a/Riot/Modules/People/PeopleViewController.m b/Riot/Modules/People/PeopleViewController.m index 4c3203774..eb004e915 100644 --- a/Riot/Modules/People/PeopleViewController.m +++ b/Riot/Modules/People/PeopleViewController.m @@ -89,23 +89,12 @@ { // Take the lead on the shared data source. recentsDataSource = (RecentsDataSource*)self.dataSource; - recentsDataSource.areSectionsShrinkable = NO; [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModePeople]; } } #pragma mark - UITableView delegate -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section -{ - return 0.0; -} - -- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - return nil; -} - - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if ([super respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m index 4b4afa3db..e1a529086 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -72,7 +72,6 @@ { // Take the lead on the shared data source. recentsDataSource = (RecentsDataSource*)self.dataSource; - recentsDataSource.areSectionsShrinkable = NO; [recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeRooms]; } } @@ -102,17 +101,6 @@ #pragma mark - UITableView delegate -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section -{ - if ([tableView numberOfSections] <= 1) - { - // Hide the header to merge Invites and Rooms into a single list. - return 0.0; - } - - return [super tableView:tableView heightForHeaderInSection:section]; -} - - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if ([super respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) diff --git a/changelog.d/5222.change b/changelog.d/5222.change new file mode 100644 index 000000000..cdaa259b7 --- /dev/null +++ b/changelog.d/5222.change @@ -0,0 +1 @@ +Move chat/room invites to dedicated sections and enable collapsing sections