diff --git a/Riot/Assets/Base.lproj/Main.storyboard b/Riot/Assets/Base.lproj/Main.storyboard index 68fcecb67d..18e5cbd7f4 100644 --- a/Riot/Assets/Base.lproj/Main.storyboard +++ b/Riot/Assets/Base.lproj/Main.storyboard @@ -503,9 +503,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h index 7f0f4a447f..eb1b0c6c6d 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h @@ -35,6 +35,7 @@ typedef NS_ENUM(NSInteger, RecentsDataSourceMode) RecentsDataSourceModeFavourites, RecentsDataSourceModePeople, RecentsDataSourceModeRooms, + RecentsDataSourceModeRoomInvites, RecentsDataSourceModeAllChats }; diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 0b15242ae7..93e9dc6bad 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -174,6 +174,12 @@ - (DiscussionsCount *)groupMissedDiscussionsCount - (RecentsDataSourceSections *)makeDataSourceSections { NSMutableArray *types = [NSMutableArray array]; + if (self.recentsDataSourceMode == RecentsDataSourceModeRoomInvites) + { + [types addObject:@(RecentsDataSourceSectionTypeInvites)]; + return [[RecentsDataSourceSections alloc] initWithSectionTypes:types.copy]; + } + if (self.crossSigningBannerDisplay != CrossSigningBannerDisplayNone) { [types addObject:@(RecentsDataSourceSectionTypeCrossSigningBanner)]; @@ -183,7 +189,7 @@ - (RecentsDataSourceSections *)makeDataSourceSections [types addObject:@(RecentsDataSourceSectionTypeSecureBackupBanner)]; } - if (!BuildSettings.newAppLayoutEnabled && self.invitesCellDataArray.count > 0) + if (self.invitesCellDataArray.count > 0) { [types addObject:@(RecentsDataSourceSectionTypeInvites)]; } @@ -229,11 +235,6 @@ - (RecentsDataSourceSections *)makeDataSourceSections [types addObject:@(RecentsDataSourceSectionTypeAllChats)]; } - if (self.currentSpace == nil && BuildSettings.newAppLayoutEnabled && self.invitesCellDataArray.count > 0) - { - [types addObject:@(RecentsDataSourceSectionTypeInvites)]; - } - if (self.currentSpace != nil && self.suggestedRoomCellDataArray.count > 0) { [types addObject:@(RecentsDataSourceSectionTypeSuggestedRooms)]; @@ -625,7 +626,13 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } else if (sectionType == RecentsDataSourceSectionTypeInvites && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_INVITES)) { - count = self.invitesCellDataArray.count; + if (self.recentsDataSourceMode == RecentsDataSourceModeAllChats) + { + count = 1; + } + else { + count = self.invitesCellDataArray.count; + } } else if (sectionType == RecentsDataSourceSectionTypeSuggestedRooms && !(shrinkedSectionsBitMask & RECENTSDATASOURCE_SECTION_SUGGESTED)) { @@ -660,6 +667,7 @@ - (CGFloat)heightForHeaderInSection:(NSInteger)section if (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner || sectionType == RecentsDataSourceSectionTypeCrossSigningBanner || sectionType == RecentsDataSourceSectionTypeBreadcrumbs || + (sectionType == RecentsDataSourceSectionTypeInvites && self.recentsDataSourceMode == RecentsDataSourceModeAllChats) || (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsFilterOptions.optionsCount)) { return 0.0; @@ -859,6 +867,7 @@ - (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame in if (sectionType == RecentsDataSourceSectionTypeSecureBackupBanner || sectionType == RecentsDataSourceSectionTypeCrossSigningBanner || sectionType == RecentsDataSourceSectionTypeBreadcrumbs || + (sectionType == RecentsDataSourceSectionTypeInvites && self.recentsDataSourceMode == RecentsDataSourceModeRoomInvites) || (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsFilterOptions.optionsCount)) { return nil; @@ -1088,6 +1097,14 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return tableViewCell; } + else if (sectionType == RecentsDataSourceSectionTypeInvites && self.recentsDataSourceMode == RecentsDataSourceModeAllChats) + { + RecentsInvitesTableViewCell *tableViewCell = [tableView dequeueReusableCellWithIdentifier:[RecentsInvitesTableViewCell defaultReuseIdentifier]]; + + tableViewCell.invitesCount = self.recentsListService.invitedRoomListData.counts.numberOfRooms; + + return tableViewCell; + } return [super tableView:tableView cellForRowAtIndexPath:indexPath]; } @@ -1199,6 +1216,10 @@ - (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath if (sectionType == RecentsDataSourceSectionTypeAllChats && !self.allChatsRoomCellDataArray.count) { return 300.0; } + if (sectionType == RecentsDataSourceSectionTypeInvites && self.recentsDataSourceMode == RecentsDataSourceModeAllChats) + { + return 32.0; + } // Override this method here to use our own cellDataAtIndexPath id cellData = [self cellDataAtIndexPath:indexPath]; @@ -1509,7 +1530,7 @@ - (void)searchWithPatterns:(NSArray *)patternsList - (BOOL)isDraggableCellAt:(NSIndexPath*)path { - if (_recentsDataSourceMode == RecentsDataSourceModePeople || _recentsDataSourceMode == RecentsDataSourceModeRooms) + if (_recentsDataSourceMode == RecentsDataSourceModePeople || _recentsDataSourceMode == RecentsDataSourceModeRooms || _recentsDataSourceMode == RecentsDataSourceModeRoomInvites) { return NO; } diff --git a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift index 85cd4208cb..7370f0f947 100644 --- a/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift +++ b/Riot/Modules/Common/Recents/Service/MatrixSDK/RecentsListService.swift @@ -35,7 +35,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { private var invitedRoomListDataFetcher: MXRoomListDataFetcher? { switch mode { - case .home, .allChats: + case .home, .allChats, .roomInvites: return invitedRoomListDataFetcherForHome case .people: return invitedRoomListDataFetcherForPeople @@ -87,6 +87,7 @@ public class RecentsListService: NSObject, RecentsListServiceProtocol { .favourites: [.favorited], .people: [.invited, .directPeople], .rooms: [.invited, .conversationRooms, .suggested], + .roomInvites: [.invited], .allChats: [.breadcrumbs, .favorited, .directHome, .invited, .allChats, .lowPriority, .serverNotice, .suggested] ] diff --git a/Riot/Modules/Common/Recents/Views/RecentsInvitesTableViewCell.swift b/Riot/Modules/Common/Recents/Views/RecentsInvitesTableViewCell.swift new file mode 100644 index 0000000000..17ff6e49c2 --- /dev/null +++ b/Riot/Modules/Common/Recents/Views/RecentsInvitesTableViewCell.swift @@ -0,0 +1,71 @@ +// +// Copyright 2022 New Vector 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 UIKit +import Reusable + +/// `RecentsInvitesTableViewCell` can be used as a placeholder to show invites number +class RecentsInvitesTableViewCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Outlet + + @IBOutlet weak private var badgeLabel: BadgeLabel! + @IBOutlet weak private var titleLabel: UILabel! + + // MARK: - Properties + + @objc var invitesCount: Int = 0 { + didSet { + badgeLabel.text = "\(invitesCount)" + } + } + + // MARK: - NibReusable + + @objc static func defaultReuseIdentifier() -> String { + return reuseIdentifier + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + setupView() + update(theme: ThemeService.shared().theme) + } + + // MARK: - Themable + + func update(theme: Theme) { + self.backgroundColor = theme.colors.background + + badgeLabel.badgeColor = theme.colors.alert + badgeLabel.textColor = theme.colors.background + badgeLabel.font = theme.fonts.footnoteSB + + titleLabel.textColor = theme.colors.accent + } + + // MARK: - Private + + private func setupView() { + self.selectionStyle = .none + + titleLabel.text = VectorL10n.roomRecentsInvitesSection.capitalized + update(theme: ThemeService.shared().theme) + } +} diff --git a/Riot/Modules/Common/Recents/Views/RecentsInvitesTableViewCell.xib b/Riot/Modules/Common/Recents/Views/RecentsInvitesTableViewCell.xib new file mode 100644 index 0000000000..25102939af --- /dev/null +++ b/Riot/Modules/Common/Recents/Views/RecentsInvitesTableViewCell.xib @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 392a09a523..cfa07a2a8f 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -56,7 +56,8 @@ class AllChatsViewController: HomeViewController { recentsTableView.tag = RecentsDataSourceMode.allChats.rawValue recentsTableView.clipsToBounds = false recentsTableView.register(RecentEmptySectionTableViewCell.nib, forCellReuseIdentifier: RecentEmptySectionTableViewCell.reuseIdentifier) - + recentsTableView.register(RecentsInvitesTableViewCell.nib, forCellReuseIdentifier: RecentsInvitesTableViewCell.reuseIdentifier) + updateUI() vc_setLargeTitleDisplayMode(.automatic) @@ -127,6 +128,51 @@ class AllChatsViewController: HomeViewController { self.spaceSelectorBridgePresenter = spaceSelectorBridgePresenter } + // MARK: - UITableViewDataSource + + private func sectionType(forSectionAt index: Int) -> RecentsDataSourceSectionType? { + guard let recentsDataSource = dataSource as? RecentsDataSource else { + return nil + } + + return recentsDataSource.sections.sectionType(forSectionIndex: index) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let sectionType = sectionType(forSectionAt: section), sectionType == .invites else { + return super.tableView(tableView, numberOfRowsInSection: section) + } + + return dataSource.tableView(tableView, numberOfRowsInSection: section) + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites else { + return super.tableView(tableView, cellForRowAt: indexPath) + } + + return dataSource.tableView(tableView, cellForRowAt: indexPath) + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites else { + return super.tableView(tableView, heightForRowAt: indexPath) + } + + return dataSource.cellHeight(at: indexPath) + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let sectionType = sectionType(forSectionAt: indexPath.section), sectionType == .invites else { + super.tableView(tableView, didSelectRowAt: indexPath) + return + } + + showRoomInviteList() + } + // MARK: - Toolbar animation private var lastScrollPosition: Double = 0 @@ -385,6 +431,16 @@ class AllChatsViewController: HomeViewController { } present(coordinator.toPresentable(), animated: true) } + + private func showRoomInviteList() { + let invitesViewController = RoomInvitesViewController.instantiate() + invitesViewController.userIndicatorStore = self.userIndicatorStore +// invitesViewController.displayList(self.dataSource) + let recentsListService = RecentsListService(withSession: mainSession) + let recentsDataSource = RecentsDataSource(matrixSession: mainSession, recentsListService: recentsListService) + invitesViewController.displayList(recentsDataSource) + self.navigationController?.pushViewController(invitesViewController, animated: true) + } } // MARK: - SpaceSelectorBottomSheetCoordinatorBridgePresenterDelegate diff --git a/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift b/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift new file mode 100644 index 0000000000..e8ff9328f2 --- /dev/null +++ b/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift @@ -0,0 +1,127 @@ +// +// Copyright 2022 New Vector 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 Foundation + +class RoomInvitesViewController: RecentsViewController { + + // MARK: - Class methods + + static override func nib() -> UINib! { + return UINib(nibName: String(describing: self), bundle: Bundle(for: self.classForCoder())) + } + + static func instantiate() -> Self { + let storyboard = UIStoryboard(name: "Main", bundle: .main) + guard let viewController = storyboard.instantiateViewController(withIdentifier: "RoomInvitesViewController") as? Self else { + fatalError("No view controller of type \(self) in the main storyboard") + } + return viewController + } + + // MARK: - Private + + private var recentsDataSource: RecentsDataSource? + private var tableViewPaginationThrottler: MXThrottler! + + // MARK: - Lifecycle + + override func awakeFromNib() { + super.awakeFromNib() + self.enableSearchBar = false + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.recentsTableView.clipsToBounds = false + self.recentsTableView.tag = RecentsDataSourceMode.roomInvites.rawValue + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + guard let recentsDataSource = self.dataSource as? RecentsDataSource else { + return + } + + self.recentsDataSource = recentsDataSource + + if recentsDataSource.recentsDataSourceMode != .roomInvites { + recentsDataSource.setDelegate(self, andRecentsDataSourceMode: .roomInvites) + recentsDataSource.search(withPatterns: nil) + recentsSearchBar?.text = nil + } + } + + // MARK: - RecentsViewController + + override func finalizeInit() { + super.finalizeInit() + + title = VectorL10n.roomRecentsInvitesSection.capitalized + // TODO: add right screen tracker +// self.screenTracker = AnalyticsScreenTracker(screen: .rooms) + tableViewPaginationThrottler = MXThrottler(minimumDelay: 0.1) + } + + override func refreshCurrentSelectedCell(_ forceVisible: Bool) { + // Check whether the recents data source is correctly configured. + guard self.recentsDataSource?.recentsDataSourceMode == .roomInvites else { + return + } + + super.refreshCurrentSelectedCell(forceVisible) + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 0 + } + + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + super.tableView(tableView, willDisplay: cell, forRowAt: indexPath) + + tableViewPaginationThrottler .throttle { [weak self] in + guard let self = self, tableView.numberOfSections > indexPath.section else { + return + } + + let numberOfRowsInSection = tableView.numberOfRows(inSection: indexPath.section) + if indexPath.row == numberOfRowsInSection - 1 { + self.recentsDataSource?.paginate(inSection: indexPath.section) + } + } + } + + // MARK: - Empty view management + + override func updateEmptyView() { + emptyView?.fill(with: self.emptyViewArtwork, + title: VectorL10n.roomsEmptyViewTitle, + informationText: VectorL10n.roomsEmptyViewInformation) + } + + private var emptyViewArtwork: UIImage { + if ThemeService.shared().isCurrentThemeDark() { + return Asset.Images.roomsEmptyScreenArtworkDark.image + } else { + return Asset.Images.roomsEmptyScreenArtwork.image + } + } + +} diff --git a/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib b/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib new file mode 100644 index 0000000000..0dbb0ef8a5 --- /dev/null +++ b/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/MatrixKit/Models/MXKDataSource.h b/Riot/Modules/MatrixKit/Models/MXKDataSource.h index a1332d6e25..d300a2d7fb 100644 --- a/Riot/Modules/MatrixKit/Models/MXKDataSource.h +++ b/Riot/Modules/MatrixKit/Models/MXKDataSource.h @@ -221,5 +221,15 @@ typedef enum : NSUInteger { */ - (BOOL)dataSource:(MXKDataSource*)dataSource shouldDoAction:(NSString *)actionIdentifier inCell:(id)cell userInfo:(NSDictionary *)userInfo defaultValue:(BOOL)defaultValue; +/** + Notify the delegate that invites count did change + + @see `MXKCellRenderingDelegate` for more details. + + @param dataSource the involved data source. + @param invitesCount number of rooms in the invites section. + */ +- (void)dataSource:(MXKDataSource*)dataSource didUpdateInvitesCount:(NSUInteger)invitesCount; + @end diff --git a/Riot/Modules/People/Views/InviteRecentTableViewCell.m b/Riot/Modules/People/Views/InviteRecentTableViewCell.m index ea71c25130..ea42b74a49 100644 --- a/Riot/Modules/People/Views/InviteRecentTableViewCell.m +++ b/Riot/Modules/People/Views/InviteRecentTableViewCell.m @@ -40,12 +40,12 @@ - (void)awakeFromNib { [super awakeFromNib]; - [self.leftButton.layer setCornerRadius:5]; + [self.leftButton.layer setCornerRadius:8]; self.leftButton.clipsToBounds = YES; [self.leftButton setTitle:[VectorL10n decline] forState:UIControlStateNormal]; [self.leftButton addTarget:self action:@selector(onDeclinePressed:) forControlEvents:UIControlEventTouchUpInside]; - [self.rightButton.layer setCornerRadius:5]; + [self.rightButton.layer setCornerRadius:8]; self.rightButton.clipsToBounds = YES; [self.rightButton setTitle:[VectorL10n accept] forState:UIControlStateNormal]; [self.rightButton addTarget:self action:@selector(onRightButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; @@ -57,8 +57,14 @@ - (void)customizeTableViewCellRendering { [super customizeTableViewCellRendering]; - self.leftButton.backgroundColor = ThemeService.shared.theme.tintColor; + self.leftButton.backgroundColor = UIColor.clearColor; + self.leftButton.layer.borderWidth = 1; + self.leftButton.layer.borderColor = ThemeService.shared.theme.colors.alert.CGColor; + self.leftButton.titleLabel.font = ThemeService.shared.theme.fonts.body; + [self.leftButton setTitleColor:ThemeService.shared.theme.colors.alert forState:UIControlStateNormal]; + self.rightButton.backgroundColor = ThemeService.shared.theme.tintColor; + self.rightButton.titleLabel.font = ThemeService.shared.theme.fonts.body; } - (void)prepareForReuse diff --git a/Riot/Modules/People/Views/InviteRecentTableViewCell.xib b/Riot/Modules/People/Views/InviteRecentTableViewCell.xib index 5911b2ba0f..4f0594d0e0 100644 --- a/Riot/Modules/People/Views/InviteRecentTableViewCell.xib +++ b/Riot/Modules/People/Views/InviteRecentTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -45,50 +45,68 @@ - - + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + - +