From 785b8662174df3a2fc8640e5d5c573857749bd59 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 1 Sep 2022 16:56:18 +0200 Subject: [PATCH 1/3] Glitchy room list header when scrolling - fixed --- .../Recents/DataSources/RecentsDataSource.m | 5 +- .../Common/Recents/RecentsViewController.m | 23 +- .../Recents/Views/RecentTableViewCell.m | 14 +- .../Home/AllChats/AllChatsCoordinator.swift | 817 ++++++++++++++++++ .../AllChats/AllChatsViewController.swift | 520 ++++++++++- .../Home/AllChats/AllChatsViewController.xib | 15 +- .../AllChats/RoomInvitesViewController.swift | 18 +- .../AllChats/RoomInvitesViewController.xib | 15 +- Riot/Modules/Home/HomeViewController.m | 63 +- ...ollerWithBannerWrapperViewController.swift | 26 - .../VersionCheck/VersionCheckBannerView.xib | 69 +- Riot/Modules/Room/MXKRoomViewController.m | 10 +- Riot/Modules/Room/RoomViewController.m | 4 +- .../SplitView/SplitViewCoordinator.swift | 11 +- ...ViewUserIndicatorPresentationContext.swift | 4 +- Riot/Modules/TabBar/TabBarCoordinator.swift | 13 +- Riot/SupportingFiles/Riot-Bridging-Header.h | 1 + changelog.d/6513.bugfix | 1 + 18 files changed, 1415 insertions(+), 214 deletions(-) create mode 100644 Riot/Modules/Home/AllChats/AllChatsCoordinator.swift delete mode 100644 Riot/Modules/Home/VersionCheck/AllChatsViewControllerWithBannerWrapperViewController.swift create mode 100644 changelog.d/6513.bugfix diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 228751d31e..c98dd254ed 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1714,7 +1714,10 @@ - (void)crossSigningSetupBannerCellDidTapCloseAction:(CrossSigningSetupBannerCel - (void)recentsListServiceDidChangeData:(id)service totalCountsChanged:(BOOL)totalCountsChanged { - [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; + if (!BuildSettings.isNewAppLayoutActivated) + { + [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; + } } - (void)recentsListServiceDidChangeData:(id)service diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 3ff5775d04..92ddc408d0 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -373,8 +373,6 @@ - (void)viewDidLayoutSubviews - (void)refreshRecentsTable { - MXLogDebug(@"[RecentsViewController]: Refreshing recents table view") - if (!self.recentsUpdateEnabled) { isRefreshNeeded = YES; @@ -384,7 +382,11 @@ - (void)refreshRecentsTable isRefreshNeeded = NO; // Refresh the tabBar icon badges - [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; + if (!BuildSettings.isNewAppLayoutActivated) + { + // Refresh the tabBar icon badges + [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; + } // do not refresh if there is a pending recent drag and drop if (movingCellPath) @@ -1103,9 +1105,12 @@ - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes [self refreshRecentsTable]; } - // Since we've enabled room list pagination, `refreshRecentsTable` not called in this case. - // Refresh tab bar badges separately. - [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; + if (!BuildSettings.isNewAppLayoutActivated) + { + // Since we've enabled room list pagination, `refreshRecentsTable` not called in this case. + // Refresh tab bar badges separately. + [[AppDelegate theDelegate].masterTabBarController refreshTabBarBadges]; + } [self showEmptyViewIfNeeded]; @@ -1513,6 +1518,12 @@ - (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButto - (void)scrollViewDidScroll:(UIScrollView *)scrollView { + if (!self.recentsSearchBar) + { + [super scrollViewDidScroll:scrollView]; + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ [self refreshStickyHeadersContainersHeight]; diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m index 6cd41134fd..c09822c15c 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -88,7 +88,7 @@ - (void)render:(MXKCellData *)cellData { self.lastEventDescription.text = roomCellData.lastEventTextMessage; } - + self.unsentImageView.hidden = roomCellData.roomSummary.sentStatus == MXRoomSummarySentStatusOk; self.lastEventDecriptionLabelTrailingConstraint.constant = self.unsentImageView.hidden ? 10 : 30; @@ -96,17 +96,17 @@ - (void)render:(MXKCellData *)cellData if (roomCellData.hasUnread) { self.missedNotifAndUnreadIndicator.hidden = NO; - + if (0 < roomCellData.notificationCount) { self.missedNotifAndUnreadIndicator.backgroundColor = roomCellData.highlightCount ? ThemeService.shared.theme.noticeColor : ThemeService.shared.theme.noticeSecondaryColor; - + self.missedNotifAndUnreadBadgeBgView.hidden = NO; self.missedNotifAndUnreadBadgeBgView.backgroundColor = self.missedNotifAndUnreadIndicator.backgroundColor; - + self.missedNotifAndUnreadBadgeLabel.text = roomCellData.notificationCountStringValue; [self.missedNotifAndUnreadBadgeLabel sizeToFit]; - + self.missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = self.missedNotifAndUnreadBadgeLabel.frame.size.width + 18; } else @@ -120,8 +120,8 @@ - (void)render:(MXKCellData *)cellData else { self.lastEventDate.textColor = ThemeService.shared.theme.textSecondaryColor; - - // The room title is not bold anymore + + // The room title is not bold anymore self.roomTitle.font = [UIFont systemFontOfSize:17 weight:UIFontWeightMedium]; } diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift new file mode 100644 index 0000000000..1fa4f376c0 --- /dev/null +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -0,0 +1,817 @@ +// +// 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 +import CommonKit + +/// AllChatsCoordinator input parameters +class AllChatsCoordinatorParameters { + + let userSessionsService: UserSessionsService + let appNavigator: AppNavigatorProtocol + + init(userSessionsService: UserSessionsService, appNavigator: AppNavigatorProtocol) { + self.userSessionsService = userSessionsService + self.appNavigator = appNavigator + } +} + +class AllChatsCoordinator: NSObject, TabBarCoordinatorType { + + // MARK: Properties + + // MARK: Private + + private let parameters: AllChatsCoordinatorParameters + private let activityIndicatorPresenter: ActivityIndicatorPresenterType + private let indicatorPresenter: UserIndicatorTypePresenterProtocol + private let userIndicatorStore: UserIndicatorStore + private var appStateIndicatorCancel: UserIndicatorCancel? + private var appSateIndicator: UserIndicator? + + // Indicate if the Coordinator has started once + private var hasStartedOnce: Bool { + return self.allChatsViewController != nil + } + + // TODO: Move MasterTabBarController navigation code here + private var allChatsViewController: AllChatsViewController! + + // TODO: Embed UINavigationController in each tab like recommended by Apple and remove these properties. UITabBarViewController shoud not be embed in a UINavigationController (https://github.com/vector-im/riot-ios/issues/3086). + private let navigationRouter: NavigationRouterType + + private var currentSpaceId: String? + + private weak var versionCheckCoordinator: VersionCheckCoordinator? + + private var currentMatrixSession: MXSession? { + return parameters.userSessionsService.mainUserSession?.matrixSession + } + + private var isAllChatsControllerTopMostController: Bool { + return self.navigationRouter.modules.last is AllChatsViewController + } + + private var detailUserIndicatorPresenter: UserIndicatorTypePresenterProtocol { + guard let presenter = splitViewMasterPresentableDelegate?.detailUserIndicatorPresenter else { + MXLog.debug("[AllChatsCoordinator]: Missing defautl user indicator presenter") + return UserIndicatorTypePresenter(presentingViewController: toPresentable()) + } + return presenter + } + + private var indicators = [UserIndicator]() + private var signOutAlertPresenter = SignOutAlertPresenter() + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: TabBarCoordinatorDelegate? + + weak var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? + + // MARK: - Setup + + init(parameters: AllChatsCoordinatorParameters) { + self.parameters = parameters + + let masterNavigationController = RiotNavigationController() + self.navigationRouter = NavigationRouter(navigationController: masterNavigationController) + self.activityIndicatorPresenter = ActivityIndicatorPresenter() + self.indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: masterNavigationController) + self.userIndicatorStore = UserIndicatorStore(presenter: indicatorPresenter) + } + + // MARK: - Public methods + + func start() { + self.start(with: nil) + } + + func start(with spaceId: String?) { + + // If start has been done once do not setup view controllers again + if self.hasStartedOnce == false { + signOutAlertPresenter.delegate = self + + let allChatsViewController = AllChatsViewController.instantiate() + allChatsViewController.allChatsDelegate = self + allChatsViewController.userIndicatorStore = UserIndicatorStore(presenter: indicatorPresenter) + createLeftButtonItem(for: allChatsViewController) + self.allChatsViewController = allChatsViewController + self.navigationRouter.setRootModule(allChatsViewController) + + // Add existing Matrix sessions if any + for userSession in self.parameters.userSessionsService.userSessions { + self.addMatrixSessionToAllChatsController(userSession.matrixSession) + } + + self.registerUserSessionsServiceNotifications() + self.registerSessionChange() + + let versionCheckCoordinator = createVersionCheckCoordinator(withRootViewController: allChatsViewController, bannerPresentrer: allChatsViewController) + versionCheckCoordinator.start() + self.add(childCoordinator: versionCheckCoordinator) + } + + self.allChatsViewController?.switchSpace(withId: spaceId) + + self.currentSpaceId = spaceId + } + + func toPresentable() -> UIViewController { + return self.navigationRouter.toPresentable() + } + + func releaseSelectedItems() { + self.allChatsViewController.releaseSelectedItem() + } + + func popToHome(animated: Bool, completion: (() -> Void)?) { + + // Force back to the main screen if this is not the one that is displayed + if allChatsViewController != self.navigationRouter.modules.last?.toPresentable() { + + // Listen to the masterNavigationController changes + // We need to be sure that allChatsViewController is back to the screen + + // If the AllChatsViewController is not visible because there is a modal above it + // but still the top view controller of navigation controller + if self.isAllChatsControllerTopMostController { + completion?() + } else { + // Otherwise AllChatsViewController is not the top controller of the navigation controller + + // Waiting for `self.navigationRouter` popping to AllChatsViewController + var token: NSObjectProtocol? + token = NotificationCenter.default.addObserver(forName: NavigationRouter.didPopModule, object: self.navigationRouter, queue: OperationQueue.main) { [weak self] (notification) in + + guard let self = self else { + return + } + + // If AllChatsViewController is now the top most controller in navigation controller stack call the completion + if self.isAllChatsControllerTopMostController { + + completion?() + + if let token = token { + NotificationCenter.default.removeObserver(token) + } + } + } + + // Pop to root view controller + self.navigationRouter.popToRootModule(animated: animated) + } + } else { + // the AllChatsViewController is already visible + completion?() + } + } + + func showErroIndicator(with error: Error) { + let error = error as NSError + + // Ignore fake error, or connection cancellation error + guard error.domain != NSURLErrorDomain || error.code != NSURLErrorCancelled else { + return + } + + // Ignore GDPR Consent not given error. Already caught by kMXHTTPClientUserConsentNotGivenErrorNotification observation + let mxError = MXError.isMXError(error) ? MXError(nsError: error) : nil + guard mxError?.errcode != kMXErrCodeStringConsentNotGiven else { + return + } + + let msg = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String + let localizedDescription = error.userInfo[NSLocalizedDescriptionKey] as? String + let title = (error.userInfo[NSLocalizedFailureReasonErrorKey] as? String) ?? (msg ?? (localizedDescription ?? VectorL10n.error)) + + indicators.append(self.indicatorPresenter.present(.failure(label: title))) + } + + func showAppStateIndicator(with text: String, icon: UIImage?) { + hideAppStateIndicator() + appSateIndicator = self.indicatorPresenter.present(.custom(label: text, icon: icon)) + } + + func hideAppStateIndicator() { + appSateIndicator?.cancel() + appSateIndicator = nil + } + + // MARK: - SplitViewMasterPresentable + + var selectedNavigationRouter: NavigationRouterType? { + return self.navigationRouter + } + + // MARK: Split view + + /// If the split view is collapsed (one column visible) it will push the Presentable on the primary navigation controller, otherwise it will show the Presentable as the secondary view of the split view. + private func replaceSplitViewDetails(with presentable: Presentable, popCompletion: (() -> Void)? = nil) { + self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToReplaceDetailWith: presentable, popCompletion: popCompletion) + } + + /// If the split view is collapsed (one column visible) it will push the Presentable on the primary navigation controller, otherwise it will show the Presentable as the secondary view of the split view on top of existing views. + private func stackSplitViewDetails(with presentable: Presentable, popCompletion: (() -> Void)? = nil) { + self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToStack: presentable, popCompletion: popCompletion) + } + + private func showSplitViewDetails(with presentable: Presentable, stackedOnSplitViewDetail: Bool, popCompletion: (() -> Void)? = nil) { + + if stackedOnSplitViewDetail { + self.stackSplitViewDetails(with: presentable, popCompletion: popCompletion) + } else { + self.replaceSplitViewDetails(with: presentable, popCompletion: popCompletion) + } + } + + private func showSplitViewDetails(with modules: [NavigationModule], stack: Bool) { + if stack { + self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToStack: modules) + } else { + self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToReplaceDetailsWith: modules) + } + } + + private func resetSplitViewDetails() { + self.splitViewMasterPresentableDelegate?.splitViewMasterPresentableWantsToResetDetail(self) + } + + // MARK: UserSessions management + + private func registerUserSessionsServiceNotifications() { + + // Listen only notifications from the current UserSessionsService instance + let userSessionService = self.parameters.userSessionsService + + NotificationCenter.default.addObserver(self, selector: #selector(userSessionsServiceDidAddUserSession(_:)), name: UserSessionsService.didAddUserSession, object: userSessionService) + + NotificationCenter.default.addObserver(self, selector: #selector(userSessionsServiceWillRemoveUserSession(_:)), name: UserSessionsService.willRemoveUserSession, object: userSessionService) + } + + @objc private func userSessionsServiceDidAddUserSession(_ notification: Notification) { + guard let userSession = notification.userInfo?[UserSessionsService.NotificationUserInfoKey.userSession] as? UserSession else { + return + } + + self.addMatrixSessionToAllChatsController(userSession.matrixSession) + } + + @objc private func userSessionsServiceWillRemoveUserSession(_ notification: Notification) { + guard let userSession = notification.userInfo?[UserSessionsService.NotificationUserInfoKey.userSession] as? UserSession else { + return + } + + self.removeMatrixSessionFromAllChatsController(userSession.matrixSession) + } + + // MARK: - Matrix Session management + + // TODO: Remove Matrix session handling from the view controller + private func addMatrixSessionToAllChatsController(_ matrixSession: MXSession) { + MXLog.debug("[TabBarCoordinator] masterTabBarController.addMatrixSession") + self.allChatsViewController.addMatrixSession(matrixSession) + } + + // TODO: Remove Matrix session handling from the view controller + private func removeMatrixSessionFromAllChatsController(_ matrixSession: MXSession) { + MXLog.debug("[TabBarCoordinator] masterTabBarController.removeMatrixSession") + self.allChatsViewController.removeMatrixSession(matrixSession) + } + + private func registerSessionChange() { + NotificationCenter.default.addObserver(self, selector: #selector(sessionDidSync(_:)), name: NSNotification.Name.mxSessionDidSync, object: nil) + } + + @objc private func sessionDidSync(_ notification: Notification) { + updateAvatarButtonItem() + } + + // MARK: Navigation + + private func showSettings() { + let viewController = self.createSettingsViewController() + + self.navigationRouter.push(viewController, animated: true, popCompletion: nil) + } + + private func showContactDetails(with contact: MXKContact, presentationParameters: ScreenPresentationParameters) { + + let coordinatorParameters = ContactDetailsCoordinatorParameters(contact: contact) + let coordinator = ContactDetailsCoordinator(parameters: coordinatorParameters) + coordinator.start() + self.add(childCoordinator: coordinator) + + self.showSplitViewDetails(with: coordinator, stackedOnSplitViewDetail: presentationParameters.stackAboveVisibleViews) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } + + // MARK: Navigation bar items management + + private weak var avatarMenuView: AvatarView? + private weak var avatarMenuButton: UIButton? + + private func createLeftButtonItem(for viewController: UIViewController) { + createAvatarButtonItem(for: viewController) + } + + private func createAvatarButtonItem(for viewController: UIViewController) { + var actions: [UIMenuElement] = [] + + actions.append(UIAction(title: VectorL10n.allChatsUserMenuSettings, image: UIImage(systemName: "gearshape")) { [weak self] action in + self?.showSettings() + }) + + var subMenuActions: [UIAction] = [] + if BuildSettings.sideMenuShowInviteFriends { + subMenuActions.append(UIAction(title: VectorL10n.sideMenuActionInviteFriends, image: UIImage(systemName: "square.and.arrow.up.fill")) { [weak self] action in + self?.showInviteFriends(from: nil) + }) + } + + subMenuActions.append(UIAction(title: VectorL10n.sideMenuActionFeedback, image: UIImage(systemName: "questionmark.circle")) { [weak self] action in + self?.showBugReport() + }) + + actions.append(UIMenu(title: "", options: .displayInline, children: subMenuActions)) + actions.append(UIMenu(title: "", options: .displayInline, children: [ + UIAction(title: VectorL10n.settingsSignOut, image: UIImage(systemName: "rectangle.portrait.and.arrow.right.fill"), attributes: .destructive) { [weak self] action in + self?.signOut() + } + ])) + + let menu = UIMenu(options: .displayInline, children: actions) + + let view = UIView(frame: CGRect(x: 0, y: 0, width: 36, height: 36)) + view.backgroundColor = .clear + + let button: UIButton = UIButton(frame: view.bounds.inset(by: UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7))) + button.setImage(Asset.Images.tabPeople.image, for: .normal) + button.menu = menu + button.showsMenuAsPrimaryAction = true + button.autoresizingMask = [.flexibleHeight, .flexibleWidth] + view.addSubview(button) + self.avatarMenuButton = button + + let avatarView = UserAvatarView(frame: view.bounds.inset(by: UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7))) + avatarView.isUserInteractionEnabled = false + avatarView.update(theme: ThemeService.shared().theme) + avatarView.autoresizingMask = [.flexibleHeight, .flexibleWidth] + view.addSubview(avatarView) + self.avatarMenuView = avatarView + + if let avatar = userAvatarViewData(from: currentMatrixSession) { + avatarView.fill(with: avatar) + button.setImage(nil, for: .normal) + } + + viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: view) + } + + private func updateAvatarButtonItem() { + guard let avatarView = avatarMenuView, let button = avatarMenuButton, let avatar = userAvatarViewData(from: currentMatrixSession) else { + return + } + + button.setImage(nil, for: .normal) + avatarView.fill(with: avatar) + } + + private func showRoom(withId roomId: String, eventId: String? = nil) { + + guard let matrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else { + return + } + + self.showRoom(with: roomId, eventId: eventId, matrixSession: matrixSession) + } + + private func showRoom(withNavigationParameters roomNavigationParameters: RoomNavigationParameters, completion: (() -> Void)?) { + + if let threadParameters = roomNavigationParameters.threadParameters, threadParameters.stackRoomScreen { + showRoomAndThread(with: roomNavigationParameters, + completion: completion) + } else { + let threadId = roomNavigationParameters.threadParameters?.threadId + let displayConfig: RoomDisplayConfiguration + if threadId != nil { + displayConfig = .forThreads + } else { + displayConfig = .default + } + + + let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + session: roomNavigationParameters.mxSession, + parentSpaceId: self.currentSpaceId, + roomId: roomNavigationParameters.roomId, + eventId: roomNavigationParameters.eventId, + threadId: threadId, + showSettingsInitially: roomNavigationParameters.showSettingsInitially, + displayConfiguration: displayConfig, + autoJoinInvitedRoom: roomNavigationParameters.autoJoinInvitedRoom) + + self.showRoom(with: roomCoordinatorParameters, + stackOnSplitViewDetail: roomNavigationParameters.presentationParameters.stackAboveVisibleViews, + completion: completion) + } + } + + private func showRoom(with roomId: String, eventId: String?, matrixSession: MXSession, completion: (() -> Void)? = nil) { + + // RoomCoordinator will be presented by the split view. + // As we don't know which navigation controller instance will be used, + // give the NavigationRouterStore instance and let it find the associated navigation controller + let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + session: matrixSession, + parentSpaceId: self.currentSpaceId, + roomId: roomId, + eventId: eventId, + showSettingsInitially: false) + + self.showRoom(with: roomCoordinatorParameters, completion: completion) + } + + private func showRoomPreview(with previewData: RoomPreviewData) { + + // RoomCoordinator will be presented by the split view + // We don't which navigation controller instance will be used + // Give the NavigationRouterStore instance and let it find the associated navigation controller if needed + let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + parentSpaceId: self.currentSpaceId, + previewData: previewData) + + self.showRoom(with: roomCoordinatorParameters) + } + + private func showRoomPreview(withNavigationParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: (() -> Void)?) { + + let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + parentSpaceId: self.currentSpaceId, + previewData: roomPreviewNavigationParameters.previewData) + + self.showRoom(with: roomCoordinatorParameters, + stackOnSplitViewDetail: roomPreviewNavigationParameters.presentationParameters.stackAboveVisibleViews, + completion: completion) + } + + private func showRoom(with parameters: RoomCoordinatorParameters, + stackOnSplitViewDetail: Bool = false, + completion: (() -> Void)? = nil) { + + // try to find the desired room screen in the stack + if let roomCoordinator = self.splitViewMasterPresentableDelegate?.detailModules.last(where: { presentable in + guard let roomCoordinator = presentable as? RoomCoordinatorProtocol else { + return false + } + return roomCoordinator.roomId == parameters.roomId + && roomCoordinator.threadId == parameters.threadId + && roomCoordinator.mxSession == parameters.session + }) as? RoomCoordinatorProtocol { + self.splitViewMasterPresentableDelegate?.splitViewMasterPresentable(self, wantsToPopTo: roomCoordinator) + // go to a specific event if provided + if let eventId = parameters.eventId { + roomCoordinator.start(withEventId: eventId, completion: completion) + } else { + completion?() + } + return + } + + let coordinator = RoomCoordinator(parameters: parameters) + coordinator.delegate = self + coordinator.start(withCompletion: completion) + self.add(childCoordinator: coordinator) + + self.showSplitViewDetails(with: coordinator, stackedOnSplitViewDetail: stackOnSplitViewDetail) { [weak self] in + // NOTE: The RoomDataSource releasing is handled in SplitViewCoordinator + self?.remove(childCoordinator: coordinator) + } + } + + private func showRoomAndThread(with roomNavigationParameters: RoomNavigationParameters, + completion: (() -> Void)? = nil) { + self.activityIndicatorPresenter.presentActivityIndicator(on: toPresentable().view, animated: false) + let dispatchGroup = DispatchGroup() + + // create room coordinator + let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + session: roomNavigationParameters.mxSession, + parentSpaceId: self.currentSpaceId, + roomId: roomNavigationParameters.roomId, + eventId: nil, + threadId: nil, + showSettingsInitially: false) + + dispatchGroup.enter() + let roomCoordinator = RoomCoordinator(parameters: roomCoordinatorParameters) + roomCoordinator.delegate = self + roomCoordinator.start { + dispatchGroup.leave() + } + self.add(childCoordinator: roomCoordinator) + + // create thread coordinator + let threadCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + session: roomNavigationParameters.mxSession, + parentSpaceId: self.currentSpaceId, + roomId: roomNavigationParameters.roomId, + eventId: roomNavigationParameters.eventId, + threadId: roomNavigationParameters.threadParameters?.threadId, + showSettingsInitially: false, + displayConfiguration: .forThreads) + + dispatchGroup.enter() + let threadCoordinator = RoomCoordinator(parameters: threadCoordinatorParameters) + threadCoordinator.delegate = self + threadCoordinator.start { + dispatchGroup.leave() + } + self.add(childCoordinator: threadCoordinator) + + dispatchGroup.notify(queue: .main) { [weak self] in + guard let self = self else { return } + let modules: [NavigationModule] = [ + NavigationModule(presentable: roomCoordinator, popCompletion: { [weak self] in + // NOTE: The RoomDataSource releasing is handled in SplitViewCoordinator + self?.remove(childCoordinator: roomCoordinator) + }), + NavigationModule(presentable: threadCoordinator, popCompletion: { [weak self] in + // NOTE: The RoomDataSource releasing is handled in SplitViewCoordinator + self?.remove(childCoordinator: threadCoordinator) + }) + ] + + self.showSplitViewDetails(with: modules, + stack: roomNavigationParameters.presentationParameters.stackAboveVisibleViews) + + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + // MARK: Sign out process + + private func signOut() { + guard let keyBackup = currentMatrixSession?.crypto.backup else { + return + } + + signOutAlertPresenter.present(for: keyBackup.state, + areThereKeysToBackup: keyBackup.hasKeysToBackup, + from: self.allChatsViewController, + sourceView: nil, + animated: true) + } + + // MARK: - SecureBackupSetupCoordinatorBridgePresenter + + private var secureBackupSetupCoordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter? + private var crossSigningSetupCoordinatorBridgePresenter: CrossSigningSetupCoordinatorBridgePresenter? + + private func showSecureBackupSetupFromSignOutFlow() { + if canSetupSecureBackup { + setupSecureBackup2() + } else { + // Set up cross-signing first + setupCrossSigning(title: VectorL10n.secureKeyBackupSetupIntroTitle, + message: VectorL10n.securitySettingsUserPasswordDescription) { [weak self] result in + switch result { + case .success(let isCompleted): + if isCompleted { + self?.setupSecureBackup2() + } + case .failure: + break + } + } + } + } + + private var canSetupSecureBackup: Bool { + return currentMatrixSession?.vc_canSetupSecureBackup() ?? false + } + + private func setupSecureBackup2() { + guard let session = currentMatrixSession else { + return + } + + let secureBackupSetupCoordinatorBridgePresenter = SecureBackupSetupCoordinatorBridgePresenter(session: session, allowOverwrite: true) + secureBackupSetupCoordinatorBridgePresenter.delegate = self + secureBackupSetupCoordinatorBridgePresenter.present(from: allChatsViewController, animated: true) + self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter + } + + private func setupCrossSigning(title: String, message: String, completion: @escaping (Result) -> Void) { + guard let session = currentMatrixSession else { + return + } + + allChatsViewController.startActivityIndicator() + allChatsViewController.view.isUserInteractionEnabled = false + + let dismissAnimation = { [weak self] in + guard let self = self else { return } + + self.allChatsViewController.stopActivityIndicator() + self.allChatsViewController.view.isUserInteractionEnabled = true + self.crossSigningSetupCoordinatorBridgePresenter?.dismiss(animated: true, completion: { + self.crossSigningSetupCoordinatorBridgePresenter = nil + }) + } + + let crossSigningSetupCoordinatorBridgePresenter = CrossSigningSetupCoordinatorBridgePresenter(session: session) + crossSigningSetupCoordinatorBridgePresenter.present(with: title, message: message, from: allChatsViewController, animated: true) { + dismissAnimation() + completion(.success(true)) + } cancel: { + dismissAnimation() + completion(.success(false)) + } failure: { error in + dismissAnimation() + completion(.failure(error)) + } + + self.crossSigningSetupCoordinatorBridgePresenter = crossSigningSetupCoordinatorBridgePresenter + } + + // MARK: - Private methods + + private func createVersionCheckCoordinator(withRootViewController rootViewController: UIViewController, bannerPresentrer: BannerPresentationProtocol) -> VersionCheckCoordinator { + let versionCheckCoordinator = VersionCheckCoordinator(rootViewController: rootViewController, + bannerPresenter: bannerPresentrer, + themeService: ThemeService.shared()) + return versionCheckCoordinator + } + + private func showInviteFriends(from sourceView: UIView?) { + let myUserId = self.parameters.userSessionsService.mainUserSession?.userId ?? "" + + let inviteFriendsPresenter = InviteFriendsPresenter() + inviteFriendsPresenter.present(for: myUserId, from: self.navigationRouter.toPresentable(), sourceView: sourceView, animated: true) + } + + private func showBugReport() { + let bugReportViewController = BugReportViewController() + + // Show in fullscreen to animate presentation along side menu dismiss + bugReportViewController.modalPresentationStyle = .fullScreen + bugReportViewController.modalTransitionStyle = .crossDissolve + + self.navigationRouter.present(bugReportViewController, animated: true) + } + + private func userAvatarViewData(from mxSession: MXSession?) -> UserAvatarViewData? { + guard let mxSession = mxSession, let userId = mxSession.myUserId, let mediaManager = mxSession.mediaManager, let myUser = mxSession.myUser else { + return nil + } + + let userDisplayName = myUser.displayname + let avatarUrl = myUser.avatarUrl + + return UserAvatarViewData(userId: userId, + displayName: userDisplayName, + avatarUrl: avatarUrl, + mediaManager: mediaManager) + } + + private func createUnifiedSearchController() -> UnifiedSearchViewController { + + let viewController: UnifiedSearchViewController = UnifiedSearchViewController.instantiate() + viewController.loadViewIfNeeded() + + for userSession in self.parameters.userSessionsService.userSessions { + viewController.addMatrixSession(userSession.matrixSession) + } + + return viewController + } + + private func createSettingsViewController() -> SettingsViewController { + let viewController: SettingsViewController = SettingsViewController.instantiate() + viewController.loadViewIfNeeded() + return viewController + } + +} + +// MARK: - SignOutAlertPresenterDelegate +extension AllChatsCoordinator: SignOutAlertPresenterDelegate { + + func signOutAlertPresenterDidTapSignOutAction(_ presenter: SignOutAlertPresenter) { + // Prevent user to perform user interaction in settings when sign out + // TODO: Prevent user interaction in all application (navigation controller and split view controller included) + allChatsViewController.view.isUserInteractionEnabled = false + allChatsViewController.startActivityIndicator() + + AppDelegate.theDelegate().logout(withConfirmation: false) { [weak self] isLoggedOut in + self?.allChatsViewController.stopActivityIndicator() + self?.allChatsViewController.view.isUserInteractionEnabled = true + } + } + + func signOutAlertPresenterDidTapBackupAction(_ presenter: SignOutAlertPresenter) { + showSecureBackupSetupFromSignOutFlow() + } + +} + +// MARK: - SecureBackupSetupCoordinatorBridgePresenterDelegate +extension AllChatsCoordinator: SecureBackupSetupCoordinatorBridgePresenterDelegate { + func secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter) { + coordinatorBridgePresenter.dismiss(animated: true) { + self.secureBackupSetupCoordinatorBridgePresenter = nil + } + } + + func secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter) { + coordinatorBridgePresenter.dismiss(animated: true) { + self.secureBackupSetupCoordinatorBridgePresenter = nil + } + } +} + +// MARK: - AllChatsViewControllerDelegate +extension AllChatsCoordinator: AllChatsViewControllerDelegate { + func allChatsViewControllerDidCompleteAuthentication(_ allChatsViewController: AllChatsViewController) { + self.delegate?.tabBarCoordinatorDidCompleteAuthentication(self) + } + + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomWithParameters roomNavigationParameters: RoomNavigationParameters, completion: @escaping () -> Void) { + self.showRoom(withNavigationParameters: roomNavigationParameters, completion: completion) + } + + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomPreviewWithParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) { + self.showRoomPreview(withNavigationParameters: roomPreviewNavigationParameters, completion: completion) + } + + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectContact contact: MXKContact, with presentationParameters: ScreenPresentationParameters) { + self.showContactDetails(with: contact, presentationParameters: presentationParameters) + } +} + +// MARK: - RoomCoordinatorDelegate +extension AllChatsCoordinator: RoomCoordinatorDelegate { + + func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) { + self.remove(childCoordinator: coordinator) + } + + func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) { + // For the moment when a room is left, reset the split detail with placeholder + self.resetSplitViewDetails() + indicatorPresenter + .present(.success(label: VectorL10n.roomParticipantsLeaveSuccess)) + .store(in: &indicators) + } + + func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) { + self.navigationRouter.popModule(animated: true) + } + + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String, eventId: String?) { + self.showRoom(withId: roomId, eventId: eventId) + } + + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didReplaceRoomWithReplacementId roomId: String) { + guard let matrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else { + return + } + + let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared, + userIndicatorPresenter: detailUserIndicatorPresenter, + session: matrixSession, + parentSpaceId: self.currentSpaceId, + roomId: roomId, + eventId: nil, + showSettingsInitially: true) + + self.showRoom(with: roomCoordinatorParameters, + stackOnSplitViewDetail: false) + } +} diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index bad683d384..7d99beabf4 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -14,9 +14,18 @@ // limitations under the License. // +// swiftlint:disable file_length + import UIKit import Reusable +protocol AllChatsViewControllerDelegate: AnyObject { + func allChatsViewControllerDidCompleteAuthentication(_ allChatsViewController: AllChatsViewController) + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomWithParameters roomNavigationParameters: RoomNavigationParameters, completion: @escaping () -> Void) + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomPreviewWithParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) + func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectContact contact: MXKContact, with presentationParameters: ScreenPresentationParameters) +} + class AllChatsViewController: HomeViewController { // MARK: - Class methods @@ -33,6 +42,10 @@ class AllChatsViewController: HomeViewController { return viewController } + // MARK: - Properties + + weak var allChatsDelegate: AllChatsViewControllerDelegate? + // MARK: - Private private let searchController = UISearchController(searchResultsController: nil) @@ -45,6 +58,40 @@ class AllChatsViewController: HomeViewController { private var childCoordinators: [Coordinator] = [] + private let tableViewPaginationThrottler = MXThrottler(minimumDelay: 0.1) + + private var reviewSessionAlertHasBeenDisplayed: Bool = false + + private var bannerView: UIView? { + didSet { + bannerView?.translatesAutoresizingMaskIntoConstraints = false + set(tableHeadeView: bannerView) + } + } + + private var isOnboardingCoordinatorPreparing: Bool = false + + private var allChatsOnboardingCoordinatorBridgePresenter: AllChatsOnboardingCoordinatorBridgePresenter? + + private var currentAlert: UIAlertController? + + // MARK: - MasterTabBarController + + // References on the currently selected room + @objc private(set) var selectedRoomId: String? + @objc private(set) var selectedEventId: String? + @objc private(set) var selectedRoomSession: MXSession? + @objc private(set) var selectedRoomPreviewData: RoomPreviewData? + + // References on the currently selected contact + @objc private(set) var selectedContact: MXKContact? + + // Reference to the current onboarding flow. It is always nil unless the flow is being presented. + @objc private(set) var onboardingCoordinatorBridgePresenter: OnboardingCoordinatorBridgePresenter? + + // Tell whether the onboarding screen is preparing. + @objc private(set) var isOnboardingInProgress: Bool = false + // MARK: - Lifecycle override func viewDidLoad() { @@ -57,9 +104,12 @@ class AllChatsViewController: HomeViewController { recentsTableView.clipsToBounds = false recentsTableView.register(RecentEmptySectionTableViewCell.nib, forCellReuseIdentifier: RecentEmptySectionTableViewCell.reuseIdentifier) recentsTableView.register(RecentsInvitesTableViewCell.nib, forCellReuseIdentifier: RecentsInvitesTableViewCell.reuseIdentifier) - + recentsTableView.contentInsetAdjustmentBehavior = .automatic + updateUI() - vc_setLargeTitleDisplayMode(.automatic) + + navigationItem.largeTitleDisplayMode = .automatic + navigationController?.navigationBar.prefersLargeTitles = true searchController.obscuresBackgroundDuringPresentation = false searchController.searchResultsUpdater = self @@ -72,12 +122,44 @@ class AllChatsViewController: HomeViewController { self.navigationController?.isToolbarHidden = false self.navigationController?.toolbar.tintColor = ThemeService.shared().theme.colors.accent - if self.tabBarController?.navigationItem.searchController == nil { - self.tabBarController?.navigationItem.searchController = searchController + if self.navigationItem.searchController == nil { + self.navigationItem.searchController = searchController } - + NotificationCenter.default.addObserver(self, selector: #selector(self.spaceListDidChange), name: MXSpaceService.didInitialise, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.spaceListDidChange), name: MXSpaceService.didBuildSpaceGraph, object: nil) + + set(tableHeadeView: self.bannerView) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Check whether we're not logged in + let authIsShown: Bool + if MXKAccountManager.shared().accounts.isEmpty { + showOnboardingFlow() + authIsShown = true + } else { + // Display a login screen if the account is soft logout + // Note: We support only one account + if let account = MXKAccountManager.shared().accounts.first, account.isSoftLogout { + showSoftLogoutOnboardingFlow(credentials: account.mxCredentials) + authIsShown = true + } else { + authIsShown = false + } + } + + guard !authIsShown else { + return + } + + AppDelegate.theDelegate().checkAppVersion() + + if BuildSettings.isNewAppLayoutActivated && !RiotSettings.shared.allChatsOnboardingHasBeenDisplayed { + self.showAllChatsOnboardingScreen() + } } override func viewWillDisappear(_ animated: Bool) { @@ -85,13 +167,60 @@ class AllChatsViewController: HomeViewController { self.navigationController?.isToolbarHidden = true } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate { context in + self.recentsTableView?.tableHeaderView?.layoutIfNeeded() + self.recentsTableView?.tableHeaderView = self.recentsTableView?.tableHeaderView + } + } - // MARK: - HomeViewController + // MARK: - Public + func switchSpace(withId spaceId: String?) { + searchController.isActive = false + + guard let spaceId = spaceId else { + self.dataSource?.currentSpace = nil + updateUI() + + return + } + + guard let space = self.mainSession.spaceService.getSpace(withId: spaceId) else { + MXLog.warning("[AllChatsViewController] switchSpace: no space found with id \(spaceId)") + return + } + + self.dataSource.currentSpace = space + updateUI() + + self.recentsTableView.setContentOffset(.zero, animated: true) + } + override var recentsDataSourceMode: RecentsDataSourceMode { .allChats } + override func addMatrixSession(_ mxSession: MXSession!) { + super.addMatrixSession(mxSession) + initDataSource() + } + + private func initDataSource() { + guard self.dataSource == nil, let mainSession = self.mxSessions.first as? MXSession else { + return + } + + MXLog.debug("[AllChatsViewController] initDataSource") + let recentsListService = RecentsListService(withSession: mainSession) + let recentsDataSource = RecentsDataSource(matrixSession: mainSession, recentsListService: recentsListService) + displayList(recentsDataSource) + recentsDataSource?.setDelegate(self, andRecentsDataSourceMode: self.recentsDataSourceMode) + } + @objc private func spaceListDidChange() { guard self.editActionProvider.shouldUpdate(with: self.mainSession, parentSpace: self.dataSource?.currentSpace) else { return @@ -118,6 +247,10 @@ class AllChatsViewController: HomeViewController { ] } + override func startActivityIndicator() { + super.startActivityIndicator() + } + // MARK: - Actions @objc private func showSpaceSelectorAction(sender: AnyObject) { @@ -174,6 +307,23 @@ class AllChatsViewController: HomeViewController { showRoomInviteList() } + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + super.tableView(tableView, willDisplay: cell, forRowAt: indexPath) + + guard let recentsDataSource = dataSource as? RecentsDataSource else { + return + } + + let sectionType = recentsDataSource.sections.sectionType(forSectionIndex: indexPath.section) + guard sectionType == .allChats, let numberOfRowsInSection = recentsDataSource.recentsListService.allChatsRoomListData?.counts.numberOfRooms, indexPath.row == numberOfRowsInSection - 1 else { + return + } + + tableViewPaginationThrottler.throttle { + recentsDataSource.paginate(inSection: indexPath.section) + } + } + // MARK: - Toolbar animation private var lastScrollPosition: Double = 0 @@ -240,13 +390,11 @@ class AllChatsViewController: HomeViewController { let shouldShowEmptyView = super.shouldShowEmptyView() if shouldShowEmptyView { - self.tabBarController?.navigationItem.searchController = nil + self.navigationItem.searchController = nil navigationItem.largeTitleDisplayMode = .never - navigationController?.navigationBar.prefersLargeTitles = false } else { - self.tabBarController?.navigationItem.searchController = searchController + self.navigationItem.searchController = searchController navigationItem.largeTitleDisplayMode = .automatic - navigationController?.navigationBar.prefersLargeTitles = true } return shouldShowEmptyView @@ -258,7 +406,7 @@ class AllChatsViewController: HomeViewController { override func userInterfaceThemeDidChange() { super.userInterfaceThemeDidChange() - guard self.tabBarController?.toolbarItems != nil else { + guard self.toolbarItems != nil else { return } @@ -271,6 +419,17 @@ class AllChatsViewController: HomeViewController { // MARK: - Private + private func set(tableHeadeView: UIView?) { + guard let tableView = recentsTableView else { + return + } + + tableView.tableHeaderView = tableHeadeView + tableView.tableHeaderView?.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true + tableView.tableHeaderView?.layoutIfNeeded() + tableView.tableHeaderView = self.recentsTableView?.tableHeaderView + } + @objc private func setupEditOptions() { guard let currentSpace = self.dataSource?.currentSpace else { updateRightNavigationItem(with: AllChatsActionProvider().menu) @@ -284,7 +443,7 @@ class AllChatsViewController: HomeViewController { private func updateUI() { let currentSpace = self.dataSource?.currentSpace - self.tabBarController?.title = currentSpace?.summary?.displayname ?? VectorL10n.allChatsTitle + self.title = currentSpace?.summary?.displayname ?? VectorL10n.allChatsTitle setupEditOptions() updateToolbar(with: editActionProvider.updateMenu(with: mainSession, parentSpace: currentSpace, completion: { [weak self] menu in @@ -294,13 +453,13 @@ class AllChatsViewController: HomeViewController { } private func updateRightNavigationItem(with menu: UIMenu) { - self.tabBarController?.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) } private func updateToolbar(with menu: UIMenu) { self.navigationController?.isToolbarHidden = false self.update(with: ThemeService.shared().theme) - self.tabBarController?.setToolbarItems([ + self.setToolbarItems([ UIBarButtonItem(image: Asset.Images.allChatsSpacesIcon.image, style: .done, target: self, action: #selector(self.showSpaceSelectorAction(sender: ))), UIBarButtonItem.flexibleSpace(), UIBarButtonItem(image: Asset.Images.allChatsEditIcon.image, menu: menu) @@ -330,27 +489,6 @@ class AllChatsViewController: HomeViewController { coordinator.start() } - private func switchSpace(withId spaceId: String?) { - searchController.isActive = false - - guard let spaceId = spaceId else { - self.dataSource.currentSpace = nil - updateUI() - - return - } - - guard let space = self.mainSession.spaceService.getSpace(withId: spaceId) else { - MXLog.warning("[AllChatsViewController] switchSpace: no space found with id \(spaceId)") - return - } - - self.dataSource.currentSpace = space - updateUI() - - self.recentsTableView.setContentOffset(.zero, animated: true) - } - private func add(childCoordinator: Coordinator) { self.childCoordinators.append(childCoordinator) } @@ -444,6 +582,21 @@ class AllChatsViewController: HomeViewController { invitesViewController.displayList(recentsDataSource) self.navigationController?.pushViewController(invitesViewController, animated: true) } + + private func showAllChatsOnboardingScreen() { + let allChatsOnboardingCoordinatorBridgePresenter = AllChatsOnboardingCoordinatorBridgePresenter() + allChatsOnboardingCoordinatorBridgePresenter.completion = { [weak self] in + RiotSettings.shared.allChatsOnboardingHasBeenDisplayed = true + + guard let self = self else { return } + self.allChatsOnboardingCoordinatorBridgePresenter?.dismiss(animated: true, completion: { + self.allChatsOnboardingCoordinatorBridgePresenter = nil + }) + } + + allChatsOnboardingCoordinatorBridgePresenter.present(from: self, animated: true) + self.allChatsOnboardingCoordinatorBridgePresenter = allChatsOnboardingCoordinatorBridgePresenter + } } // MARK: - SpaceSelectorBottomSheetCoordinatorBridgePresenterDelegate @@ -565,3 +718,300 @@ extension AllChatsViewController: SpaceMembersCoordinatorDelegate { } } + +// MARK: - BannerPresentationProtocol +extension AllChatsViewController: BannerPresentationProtocol { + func presentBannerView(_ bannerView: UIView, animated: Bool) { + self.bannerView = bannerView + } + + func dismissBannerView(animated: Bool) { + self.bannerView = nil + } +} + +// TODO: The `MasterTabBarViewController` is called from the entire app through the `LegacyAppDelegate`. this part of the code should be moved into `AppCoordinator` +// MARK: - MasterTabBarViewController +extension AllChatsViewController { + + /// Release the current selected item (if any). + @objc func releaseSelectedItem() { + selectedRoomId = nil + selectedEventId = nil + selectedRoomSession = nil + selectedRoomPreviewData = nil + selectedContact = nil + } + + /// Refresh the missed conversations badges on tab bar icon + @objc func refreshTabBarBadges() { + // Nothing to do here as we don't have tab bar + } + + /// Verify the current device if needed. + /// + /// - Parameters: + /// - session: the matrix session. + @objc func presentVerifyCurrentSessionAlertIfNeeded(session: MXSession) { + guard !RiotSettings.shared.hideVerifyThisSessionAlert, !reviewSessionAlertHasBeenDisplayed, !isOnboardingInProgress else { + return + } + + reviewSessionAlertHasBeenDisplayed = true + + // Force verification if required by the HS configuration + guard !session.vc_homeserverConfiguration().encryption.isSecureBackupRequired else { + MXLog.debug("[AllChatsViewController] presentVerifyCurrentSessionAlertIfNeededWithSession: Force verification of the device") + AppDelegate.theDelegate().presentCompleteSecurity(for: session) + return + } + + presentVerifyCurrentSessionAlert(with: session) + } + + /// Verify others device if needed. + /// + /// - Parameters: + /// - session: the matrix session. + @objc func presentReviewUnverifiedSessionsAlertIfNeeded(session: MXSession) { + guard !RiotSettings.shared.hideReviewSessionsAlert, !reviewSessionAlertHasBeenDisplayed else { + return + } + + let devices = mainSession.crypto.devices(forUser: mainSession.myUserId).values + var userHasOneUnverifiedDevice = false + for device in devices { + if !device.trustLevel.isCrossSigningVerified { + userHasOneUnverifiedDevice = true + break + } + } + + if userHasOneUnverifiedDevice { + reviewSessionAlertHasBeenDisplayed = true + presentReviewUnverifiedSessionsAlert(with: session) + } + } + + @objc func showOnboardingFlow() { + MXLog.debug("[AllChatsViewController] showOnboardingFlow") + self.showOnboardingFlowAndResetSessionFlags(true) + } + + /// Display the onboarding flow configured to log back into a soft logout session. + /// + /// - Parameters: + /// - credentials: the credentials of the soft logout session. + @objc func showSoftLogoutOnboardingFlow(credentials: MXCredentials?) { + // This method can be called after the user chooses to clear their data as the MXSession + // is opened to call logout from. So we only set the credentials when authentication isn't + // in progress to prevent a second soft logout screen being shown. + guard self.onboardingCoordinatorBridgePresenter == nil && !self.isOnboardingCoordinatorPreparing else { + return + } + + MXLog.debug("[AllChatsViewController] showAuthenticationScreenAfterSoftLogout") + AuthenticationService.shared.softLogoutCredentials = credentials + self.showOnboardingFlowAndResetSessionFlags(false) + } + + /// Open the room with the provided identifier in a specific matrix session. + /// + /// - Parameters: + /// - parameters: the presentation parameters that contains room information plus display information. + /// - completion: the block to execute at the end of the operation. + @objc func selectRoom(parameters: RoomNavigationParameters, completion: @escaping () -> Void) { + releaseSelectedItem() + + selectedRoomId = parameters.roomId + selectedEventId = parameters.eventId + selectedRoomSession = parameters.mxSession + + allChatsDelegate?.allChatsViewController(self, didSelectRoomWithParameters: parameters, completion: completion) + + refreshSelectedControllerSelectedCellIfNeeded() + } + + /// Open the RoomViewController to display the preview of a room that is unknown for the user. + /// This room can come from an email invitation link or a simple link to a room. + /// - Parameters: + /// - parameters: the presentation parameters that contains room preview information plus display information. + /// - completion: the block to execute at the end of the operation. + @objc func selectRoomPreview(parameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) { + releaseSelectedItem() + + let roomPreviewData = parameters.previewData + + selectedRoomPreviewData = roomPreviewData + selectedRoomId = roomPreviewData.roomId + selectedRoomSession = roomPreviewData.mxSession + + allChatsDelegate?.allChatsViewController(self, didSelectRoomPreviewWithParameters: parameters, completion: completion) + + refreshSelectedControllerSelectedCellIfNeeded() + } + + /// Open a ContactDetailsViewController to display the information of the provided contact. + @objc func selectContact(_ contact: MXKContact) { + let presentationParameters = ScreenPresentationParameters(restoreInitialDisplay: true, stackAboveVisibleViews: false) + selectContact(contact, withPresentationParameters: presentationParameters) + } + + /// Open a ContactDetailsViewController to display the information of the provided contact. + @objc func selectContact(_ contact: MXKContact, withPresentationParameters presentationParameters: ScreenPresentationParameters) { + releaseSelectedItem() + + selectedContact = contact + + allChatsDelegate?.allChatsViewController(self, didSelectContact: contact, with: presentationParameters) + + refreshSelectedControllerSelectedCellIfNeeded() + } + + /// The current number of rooms with missed notifications, including the invites. + @objc func missedDiscussionsCount() -> UInt { + guard let session = mxSessions as? [MXSession] else { + return 0 + } + + return session.reduce(0) { $0 + $1.vc_missedDiscussionsCount() } + } + + /// The current number of rooms with unread highlighted messages. + @objc func missedHighlightDiscussionsCount() -> UInt { + guard let session = mxSessions as? [MXSession] else { + return 0 + } + + return session.reduce(0) { $0 + $1.missedHighlightDiscussionsCount() } + } + + /// Emulated `UItabBarViewController.selectedViewController` member + @objc var selectedViewController: UIViewController? { + return self + } + + // MARK: - Private + + private func presentVerifyCurrentSessionAlert(with session: MXSession) { + MXLog.debug("[AllChatsViewController] presentVerifyCurrentSessionAlertWithSession") + + currentAlert?.dismiss(animated: true, completion: nil) + + let alert = UIAlertController(title: VectorL10n.keyVerificationSelfVerifyCurrentSessionAlertTitle, + message: VectorL10n.keyVerificationSelfVerifyCurrentSessionAlertMessage, + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: VectorL10n.keyVerificationSelfVerifyCurrentSessionAlertValidateAction, + style: .default, + handler: { action in + AppDelegate.theDelegate().presentCompleteSecurity(for: session) + })) + + alert.addAction(UIAlertAction(title: VectorL10n.later, style: .cancel)) + + alert.addAction(UIAlertAction(title: VectorL10n.doNotAskAgain, + style: .destructive, + handler: { action in + RiotSettings.shared.hideVerifyThisSessionAlert = true + })) + + self.present(alert, animated: true) + currentAlert = alert + } + + private func presentReviewUnverifiedSessionsAlert(with session: MXSession) { + MXLog.debug("[AllChatsViewController] presentReviewUnverifiedSessionsAlert") + + currentAlert?.dismiss(animated: true, completion: nil) + + let alert = UIAlertController(title: VectorL10n.keyVerificationSelfVerifyUnverifiedSessionsAlertTitle, + message: VectorL10n.keyVerificationSelfVerifyUnverifiedSessionsAlertMessage, + preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: VectorL10n.keyVerificationSelfVerifyUnverifiedSessionsAlertValidateAction, + style: .default, + handler: { action in + self.showSettingsSecurityScreen(with: session) + })) + + alert.addAction(UIAlertAction(title: VectorL10n.later, style: .cancel)) + + alert.addAction(UIAlertAction(title: VectorL10n.doNotAskAgain, style: .destructive, handler: { action in + RiotSettings.shared.hideReviewSessionsAlert = true + })) + + present(alert, animated: true) + currentAlert = alert + } + + private func showSettingsSecurityScreen(with session: MXSession) { + guard let settingsViewController = SettingsViewController.instantiate() else { + MXLog.warning("[AllChatsViewController] showSettingsSecurityScreen: cannot instantiate SettingsViewController") + return + } + + guard let securityViewController = SecurityViewController.instantiate(withMatrixSession: session) else { + MXLog.warning("[AllChatsViewController] showSettingsSecurityScreen: cannot instantiate SecurityViewController") + return + } + + settingsViewController.loadViewIfNeeded() + AppDelegate.theDelegate().restoreInitialDisplay { + self.navigationController?.viewControllers = [self, settingsViewController, securityViewController] + } + } + + private func showOnboardingFlowAndResetSessionFlags(_ resetSessionFlags: Bool) { + // Check whether an authentication screen is not already shown or preparing + guard self.onboardingCoordinatorBridgePresenter == nil && !self.isOnboardingCoordinatorPreparing else { + return + } + + self.isOnboardingCoordinatorPreparing = true + self.isOnboardingInProgress = true + + if resetSessionFlags { + resetReviewSessionsFlags() + } + + AppDelegate.theDelegate().restoreInitialDisplay { + self.presentOnboardingFlow() + } + } + + private func resetReviewSessionsFlags() { + reviewSessionAlertHasBeenDisplayed = false + RiotSettings.shared.hideVerifyThisSessionAlert = false + RiotSettings.shared.hideReviewSessionsAlert = false + } + + private func presentOnboardingFlow() { + MXLog.debug("[AllChatsViewController] presentOnboardingFlow") + + let onboardingCoordinatorBridgePresenter = OnboardingCoordinatorBridgePresenter() + onboardingCoordinatorBridgePresenter.completion = { [weak self] in + guard let self = self else { return } + + self.onboardingCoordinatorBridgePresenter?.dismiss(animated: true, completion: { + self.onboardingCoordinatorBridgePresenter = nil + }) + + self.isOnboardingInProgress = false // Must be set before calling didCompleteAuthentication + self.allChatsDelegate?.allChatsViewControllerDidCompleteAuthentication(self) + } + + onboardingCoordinatorBridgePresenter.present(from: self, animated: true) + self.onboardingCoordinatorBridgePresenter = onboardingCoordinatorBridgePresenter + self.isOnboardingCoordinatorPreparing = false + } + + private func refreshSelectedControllerSelectedCellIfNeeded() { + guard splitViewController != nil else { + return + } + + // Refresh selected cell without scrolling the selected cell (We suppose it's visible here) + self.refreshCurrentSelectedCell(false) + } +} diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.xib b/Riot/Modules/Home/AllChats/AllChatsViewController.xib index 20803b4267..83b4849ab8 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.xib +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.xib @@ -13,8 +13,6 @@ - - @@ -27,14 +25,6 @@ - - - - - - - - @@ -49,14 +39,11 @@ - - - - + diff --git a/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift b/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift index ead8dc802f..e912d491e8 100644 --- a/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift +++ b/Riot/Modules/Home/AllChats/RoomInvitesViewController.swift @@ -95,15 +95,17 @@ class RoomInvitesViewController: RecentsViewController { override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { super.tableView(tableView, willDisplay: cell, forRowAt: indexPath) + guard tableView.numberOfSections > indexPath.section else { + return + } + + let numberOfRowsInSection = tableView.numberOfRows(inSection: indexPath.section) + guard indexPath.row == numberOfRowsInSection - 1 else { + return + } + 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) - } + self?.recentsDataSource?.paginate(inSection: indexPath.section) } } diff --git a/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib b/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib index 0dbb0ef8a5..2cfe230477 100644 --- a/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib +++ b/Riot/Modules/Home/AllChats/RoomInvitesViewController.xib @@ -13,8 +13,6 @@ - - @@ -27,14 +25,6 @@ - - - - - - - - @@ -48,15 +38,12 @@ + - - - - diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index c46e9d5779..684a40ec3f 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -50,7 +50,6 @@ @interface HomeViewController () recentsDataSource paginateInSection:section]; - } - }]; -} - #pragma mark - UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section @@ -691,19 +664,21 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { + NSInteger collectionViewSection = indexPath.section; + if (collectionView.numberOfSections <= collectionViewSection) + { + return; + } + + NSInteger numberOfItemsInSection = [collectionView numberOfItemsInSection:collectionViewSection]; + if (indexPath.item != numberOfItemsInSection - 1) + { + return; + } + [self.collectionViewPaginationThrottler throttle:^{ - NSInteger collectionViewSection = indexPath.section; - if (collectionView.numberOfSections <= collectionViewSection) - { - return; - } - - NSInteger numberOfItemsInSection = [collectionView numberOfItemsInSection:collectionViewSection]; - if (indexPath.item == numberOfItemsInSection - 1) - { - NSInteger tableViewSection = collectionView.tag; - [self->recentsDataSource paginateInSection:tableViewSection]; - } + NSInteger tableViewSection = collectionView.tag; + [self->recentsDataSource paginateInSection:tableViewSection]; }]; } diff --git a/Riot/Modules/Home/VersionCheck/AllChatsViewControllerWithBannerWrapperViewController.swift b/Riot/Modules/Home/VersionCheck/AllChatsViewControllerWithBannerWrapperViewController.swift deleted file mode 100644 index 9a2a332ac1..0000000000 --- a/Riot/Modules/Home/VersionCheck/AllChatsViewControllerWithBannerWrapperViewController.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// 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 AllChatsViewControllerWithBannerWrapperViewController: HomeViewControllerWithBannerWrapperViewController { - - // MARK: - MasterTabBarItemDisplayProtocol - - override var masterTabBarItemTitle: String { - return VectorL10n.allChatsTitle - } -} diff --git a/Riot/Modules/Home/VersionCheck/VersionCheckBannerView.xib b/Riot/Modules/Home/VersionCheck/VersionCheckBannerView.xib index 82c611040b..592d20b4b2 100644 --- a/Riot/Modules/Home/VersionCheck/VersionCheckBannerView.xib +++ b/Riot/Modules/Home/VersionCheck/VersionCheckBannerView.xib @@ -1,46 +1,29 @@ - + - + - - + + - - - - - - - - - - - - - + + + - + + + + + - - - - + + + + + @@ -72,7 +67,7 @@ - + diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index e007b8595d..a720744ccb 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -221,6 +221,11 @@ - (void)viewDidLoad { [super viewDidLoad]; + if (BuildSettings.isNewAppLayoutActivated) + { + [self vc_setLargeTitleDisplayMode: UINavigationItemLargeTitleDisplayModeNever]; + } + // Check whether the view controller has been pushed via storyboard if (!_bubblesTableView) { @@ -340,11 +345,6 @@ - (void)viewWillAppear:(BOOL)animated { _bubblesTableView.hidden = NO; } - - if (BuildSettings.isNewAppLayoutActivated) - { - [self vc_setLargeTitleDisplayMode: UINavigationItemLargeTitleDisplayModeNever]; - } } - (void)viewDidAppear:(BOOL)animated diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 7d3da462fb..fb66af2e71 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -456,7 +456,7 @@ - (void)userInterfaceThemeDidChange radius:6 opacity:0.2]; - self.inputBackgroundView.backgroundColor = [ThemeService.shared.theme.backgroundColor colorWithAlphaComponent:0.98]; +// self.inputBackgroundView.backgroundColor = [ThemeService.shared.theme.backgroundColor colorWithAlphaComponent:0.98]; if (ThemeService.shared.isCurrentThemeDark) { @@ -546,6 +546,8 @@ - (void)viewWillAppear:(BOOL)animated } [self updateTopBanners]; + + self.bubblesTableView.clipsToBounds = NO; } - (void)viewWillDisappear:(BOOL)animated diff --git a/Riot/Modules/SplitView/SplitViewCoordinator.swift b/Riot/Modules/SplitView/SplitViewCoordinator.swift index a3de10ee54..f1d94e043a 100644 --- a/Riot/Modules/SplitView/SplitViewCoordinator.swift +++ b/Riot/Modules/SplitView/SplitViewCoordinator.swift @@ -93,8 +93,7 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { self.splitViewController.delegate = self // Create primary controller - let tabBarCoordinator = self.createTabBarCoordinator() - tabBarCoordinator.delegate = self + let tabBarCoordinator: TabBarCoordinatorType = BuildSettings.isNewAppLayoutActivated ? self.createAllChatsCoordinator() : self.createTabBarCoordinator() tabBarCoordinator.splitViewMasterPresentableDelegate = self tabBarCoordinator.start(with: spaceId) @@ -174,6 +173,14 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { return PlaceholderDetailViewController.instantiate() } + private func createAllChatsCoordinator() -> AllChatsCoordinator { + let coordinatorParameters = AllChatsCoordinatorParameters(userSessionsService: self.parameters.userSessionsService, appNavigator: self.parameters.appNavigator) + + let coordinator = AllChatsCoordinator(parameters: coordinatorParameters) + coordinator.delegate = self + return coordinator + } + private func createTabBarCoordinator() -> TabBarCoordinator { let coordinatorParameters = TabBarCoordinatorParameters(userSessionsService: self.parameters.userSessionsService, appNavigator: self.parameters.appNavigator) diff --git a/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift b/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift index d379e8a10c..65197831a1 100644 --- a/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift +++ b/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift @@ -19,12 +19,12 @@ import CommonKit class SplitViewUserIndicatorPresentationContext: UserIndicatorPresentationContext { private weak var splitViewController: UISplitViewController? - private weak var tabBarCoordinator: TabBarCoordinator? + private weak var tabBarCoordinator: TabBarCoordinatorType? private weak var detailNavigationController: UINavigationController? init( splitViewController: UISplitViewController, - tabBarCoordinator: TabBarCoordinator, + tabBarCoordinator: TabBarCoordinatorType, detailNavigationController: UINavigationController ) { self.splitViewController = splitViewController diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 711e1e1cf9..935d21a43f 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -297,17 +297,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { return versionCheckCoordinator } - private func createAllChatsViewController() -> AllChatsViewControllerWithBannerWrapperViewController { - let allChatsViewController = AllChatsViewController.instantiate() - allChatsViewController.tabBarItem.tag = Int(TABBAR_HOME_INDEX) - allChatsViewController.tabBarItem.image = allChatsViewController.tabBarItem.image - allChatsViewController.accessibilityLabel = VectorL10n.allChatsTitle - allChatsViewController.userIndicatorStore = UserIndicatorStore(presenter: indicatorPresenter) - - let wrapperViewController = AllChatsViewControllerWithBannerWrapperViewController(viewController: allChatsViewController) - return wrapperViewController - } - private func createHomeViewController() -> HomeViewControllerWithBannerWrapperViewController { let homeViewController: HomeViewController = HomeViewController.instantiate() homeViewController.tabBarItem.tag = Int(TABBAR_HOME_INDEX) @@ -378,7 +367,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { private func updateTabControllers(for tabBarController: MasterTabBarController, showCommunities: Bool) { var viewControllers: [UIViewController] = [] - let homeViewController = BuildSettings.isNewAppLayoutActivated ? self.createAllChatsViewController() : self.createHomeViewController() + let homeViewController = self.createHomeViewController() viewControllers.append(homeViewController) if !BuildSettings.isNewAppLayoutActivated { diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 7eacc6f914..d36cedbd4d 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -70,3 +70,4 @@ #import "UIAlertController+MatrixKit.h" #import "MXKMessageTextView.h" #import "AllChatsLayoutModels.h" +#import "SecurityViewController.h" diff --git a/changelog.d/6513.bugfix b/changelog.d/6513.bugfix new file mode 100644 index 0000000000..78f40969ad --- /dev/null +++ b/changelog.d/6513.bugfix @@ -0,0 +1 @@ +Glitchy room list header when scrolling From 46c0e5b321454f489e6d83127e72938634454cee Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Thu, 1 Sep 2022 21:57:04 +0200 Subject: [PATCH 2/3] Glitchy room list header when scrolling - added change log --- changelog.d/6537.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6537.bugfix diff --git a/changelog.d/6537.bugfix b/changelog.d/6537.bugfix new file mode 100644 index 0000000000..ee562a1c63 --- /dev/null +++ b/changelog.d/6537.bugfix @@ -0,0 +1 @@ +Performance issues with new App Layout From 356824b1f49949f6fb657d73129f7b3287d70ef1 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Fri, 2 Sep 2022 22:30:19 +0200 Subject: [PATCH 3/3] Glitchy room list header when scrolling - update after review --- Riot/Modules/Application/LegacyAppDelegate.h | 3 +- .../Home/AllChats/AllChatsCoordinator.swift | 6 +- .../AllChats/AllChatsViewController.swift | 54 +++++---- Riot/Modules/Room/RoomViewController.m | 2 +- .../SplitView/SplitViewCoordinator.swift | 34 +++--- .../SplitViewMasterViewControllerProtocol.h | 112 ++++++++++++++++++ ...ViewUserIndicatorPresentationContext.swift | 10 +- Riot/Modules/TabBar/MasterTabBarController.h | 84 +------------ Riot/Modules/TabBar/MasterTabBarController.m | 25 ++-- ... SplitViewMasterCoordinatorProtocol.swift} | 12 +- Riot/Modules/TabBar/TabBarCoordinator.swift | 6 +- 11 files changed, 193 insertions(+), 155 deletions(-) create mode 100644 Riot/Modules/SplitView/SplitViewMasterViewControllerProtocol.h rename Riot/Modules/TabBar/{TabBarCoordinatorType.swift => SplitViewMasterCoordinatorProtocol.swift} (76%) diff --git a/Riot/Modules/Application/LegacyAppDelegate.h b/Riot/Modules/Application/LegacyAppDelegate.h index b4ed788525..40faf96b42 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.h +++ b/Riot/Modules/Application/LegacyAppDelegate.h @@ -28,6 +28,7 @@ @protocol Configurable; @protocol LegacyAppDelegateDelegate; +@protocol SplitViewMasterViewControllerProtocol; @class CallBar; @class CallPresenter; @class RoomNavigationParameters; @@ -69,7 +70,7 @@ UINavigationControllerDelegate /** Application main view controller */ -@property (nonatomic, readonly) MasterTabBarController *masterTabBarController; +@property (nonatomic, readonly) UIViewController* masterTabBarController; @property (strong, nonatomic) UIWindow *window; diff --git a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift index 1fa4f376c0..052572736e 100644 --- a/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift +++ b/Riot/Modules/Home/AllChats/AllChatsCoordinator.swift @@ -29,7 +29,7 @@ class AllChatsCoordinatorParameters { } } -class AllChatsCoordinator: NSObject, TabBarCoordinatorType { +class AllChatsCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { // MARK: Properties @@ -81,7 +81,7 @@ class AllChatsCoordinator: NSObject, TabBarCoordinatorType { // Must be used only internally var childCoordinators: [Coordinator] = [] - weak var delegate: TabBarCoordinatorDelegate? + weak var delegate: SplitViewMasterCoordinatorDelegate? weak var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? @@ -759,7 +759,7 @@ extension AllChatsCoordinator: SecureBackupSetupCoordinatorBridgePresenterDelega // MARK: - AllChatsViewControllerDelegate extension AllChatsCoordinator: AllChatsViewControllerDelegate { func allChatsViewControllerDidCompleteAuthentication(_ allChatsViewController: AllChatsViewController) { - self.delegate?.tabBarCoordinatorDidCompleteAuthentication(self) + self.delegate?.splitViewMasterCoordinatorDidCompleteAuthentication(self) } func allChatsViewController(_ allChatsViewController: AllChatsViewController, didSelectRoomWithParameters roomNavigationParameters: RoomNavigationParameters, completion: @escaping () -> Void) { diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 7d99beabf4..27901c2663 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -75,22 +75,22 @@ class AllChatsViewController: HomeViewController { private var currentAlert: UIAlertController? - // MARK: - MasterTabBarController + // MARK: - SplitViewMasterViewControllerProtocol // References on the currently selected room - @objc private(set) var selectedRoomId: String? - @objc private(set) var selectedEventId: String? - @objc private(set) var selectedRoomSession: MXSession? - @objc private(set) var selectedRoomPreviewData: RoomPreviewData? + private(set) var selectedRoomId: String? + private(set) var selectedEventId: String? + private(set) var selectedRoomSession: MXSession? + private(set) var selectedRoomPreviewData: RoomPreviewData? // References on the currently selected contact - @objc private(set) var selectedContact: MXKContact? + private(set) var selectedContact: MXKContact? // Reference to the current onboarding flow. It is always nil unless the flow is being presented. - @objc private(set) var onboardingCoordinatorBridgePresenter: OnboardingCoordinatorBridgePresenter? + private(set) var onboardingCoordinatorBridgePresenter: OnboardingCoordinatorBridgePresenter? // Tell whether the onboarding screen is preparing. - @objc private(set) var isOnboardingInProgress: Bool = false + private(set) var isOnboardingInProgress: Bool = false // MARK: - Lifecycle @@ -144,7 +144,7 @@ class AllChatsViewController: HomeViewController { // Display a login screen if the account is soft logout // Note: We support only one account if let account = MXKAccountManager.shared().accounts.first, account.isSoftLogout { - showSoftLogoutOnboardingFlow(credentials: account.mxCredentials) + showSoftLogoutOnboardingFlow(with: account.mxCredentials) authIsShown = true } else { authIsShown = false @@ -731,11 +731,11 @@ extension AllChatsViewController: BannerPresentationProtocol { } // TODO: The `MasterTabBarViewController` is called from the entire app through the `LegacyAppDelegate`. this part of the code should be moved into `AppCoordinator` -// MARK: - MasterTabBarViewController -extension AllChatsViewController { +// MARK: - SplitViewMasterViewControllerProtocol +extension AllChatsViewController: SplitViewMasterViewControllerProtocol { /// Release the current selected item (if any). - @objc func releaseSelectedItem() { + func releaseSelectedItem() { selectedRoomId = nil selectedEventId = nil selectedRoomSession = nil @@ -744,7 +744,7 @@ extension AllChatsViewController { } /// Refresh the missed conversations badges on tab bar icon - @objc func refreshTabBarBadges() { + func refreshTabBarBadges() { // Nothing to do here as we don't have tab bar } @@ -752,7 +752,7 @@ extension AllChatsViewController { /// /// - Parameters: /// - session: the matrix session. - @objc func presentVerifyCurrentSessionAlertIfNeeded(session: MXSession) { + func presentVerifyCurrentSessionAlertIfNeeded(with session: MXSession) { guard !RiotSettings.shared.hideVerifyThisSessionAlert, !reviewSessionAlertHasBeenDisplayed, !isOnboardingInProgress else { return } @@ -773,7 +773,7 @@ extension AllChatsViewController { /// /// - Parameters: /// - session: the matrix session. - @objc func presentReviewUnverifiedSessionsAlertIfNeeded(session: MXSession) { + func presentReviewUnverifiedSessionsAlertIfNeeded(with session: MXSession) { guard !RiotSettings.shared.hideReviewSessionsAlert, !reviewSessionAlertHasBeenDisplayed else { return } @@ -793,7 +793,7 @@ extension AllChatsViewController { } } - @objc func showOnboardingFlow() { + func showOnboardingFlow() { MXLog.debug("[AllChatsViewController] showOnboardingFlow") self.showOnboardingFlowAndResetSessionFlags(true) } @@ -802,7 +802,7 @@ extension AllChatsViewController { /// /// - Parameters: /// - credentials: the credentials of the soft logout session. - @objc func showSoftLogoutOnboardingFlow(credentials: MXCredentials?) { + func showSoftLogoutOnboardingFlow(with credentials: MXCredentials?) { // This method can be called after the user chooses to clear their data as the MXSession // is opened to call logout from. So we only set the credentials when authentication isn't // in progress to prevent a second soft logout screen being shown. @@ -820,7 +820,7 @@ extension AllChatsViewController { /// - Parameters: /// - parameters: the presentation parameters that contains room information plus display information. /// - completion: the block to execute at the end of the operation. - @objc func selectRoom(parameters: RoomNavigationParameters, completion: @escaping () -> Void) { + func selectRoom(with parameters: RoomNavigationParameters, completion: @escaping () -> Void) { releaseSelectedItem() selectedRoomId = parameters.roomId @@ -837,7 +837,7 @@ extension AllChatsViewController { /// - Parameters: /// - parameters: the presentation parameters that contains room preview information plus display information. /// - completion: the block to execute at the end of the operation. - @objc func selectRoomPreview(parameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) { + func selectRoomPreview(with parameters: RoomPreviewNavigationParameters, completion: @escaping () -> Void) { releaseSelectedItem() let roomPreviewData = parameters.previewData @@ -852,13 +852,13 @@ extension AllChatsViewController { } /// Open a ContactDetailsViewController to display the information of the provided contact. - @objc func selectContact(_ contact: MXKContact) { + func select(_ contact: MXKContact) { let presentationParameters = ScreenPresentationParameters(restoreInitialDisplay: true, stackAboveVisibleViews: false) - selectContact(contact, withPresentationParameters: presentationParameters) + select(contact, with: presentationParameters) } /// Open a ContactDetailsViewController to display the information of the provided contact. - @objc func selectContact(_ contact: MXKContact, withPresentationParameters presentationParameters: ScreenPresentationParameters) { + func select(_ contact: MXKContact, with presentationParameters: ScreenPresentationParameters) { releaseSelectedItem() selectedContact = contact @@ -869,7 +869,7 @@ extension AllChatsViewController { } /// The current number of rooms with missed notifications, including the invites. - @objc func missedDiscussionsCount() -> UInt { + func missedDiscussionsCount() -> UInt { guard let session = mxSessions as? [MXSession] else { return 0 } @@ -878,7 +878,7 @@ extension AllChatsViewController { } /// The current number of rooms with unread highlighted messages. - @objc func missedHighlightDiscussionsCount() -> UInt { + func missedHighlightDiscussionsCount() -> UInt { guard let session = mxSessions as? [MXSession] else { return 0 } @@ -887,10 +887,14 @@ extension AllChatsViewController { } /// Emulated `UItabBarViewController.selectedViewController` member - @objc var selectedViewController: UIViewController? { + var selectedViewController: UIViewController? { return self } + var tabBar: UITabBar? { + return nil + } + // MARK: - Private private func presentVerifyCurrentSessionAlert(with session: MXSession) { diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index fb66af2e71..bb0767c471 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -456,7 +456,7 @@ - (void)userInterfaceThemeDidChange radius:6 opacity:0.2]; -// self.inputBackgroundView.backgroundColor = [ThemeService.shared.theme.backgroundColor colorWithAlphaComponent:0.98]; + self.inputBackgroundView.backgroundColor = [ThemeService.shared.theme.backgroundColor colorWithAlphaComponent:0.98]; if (ThemeService.shared.isCurrentThemeDark) { diff --git a/Riot/Modules/SplitView/SplitViewCoordinator.swift b/Riot/Modules/SplitView/SplitViewCoordinator.swift index f1d94e043a..5365db16be 100644 --- a/Riot/Modules/SplitView/SplitViewCoordinator.swift +++ b/Riot/Modules/SplitView/SplitViewCoordinator.swift @@ -56,7 +56,7 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { return self.masterPresentable?.selectedNavigationRouter } - private weak var tabBarCoordinator: TabBarCoordinatorType? + private weak var masterCoordinator: SplitViewMasterCoordinatorProtocol? // Indicate if coordinator has been started once private var hasStartedOnce: Bool = false @@ -93,29 +93,29 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { self.splitViewController.delegate = self // Create primary controller - let tabBarCoordinator: TabBarCoordinatorType = BuildSettings.isNewAppLayoutActivated ? self.createAllChatsCoordinator() : self.createTabBarCoordinator() - tabBarCoordinator.splitViewMasterPresentableDelegate = self - tabBarCoordinator.start(with: spaceId) + let masterCoordinator: SplitViewMasterCoordinatorProtocol = BuildSettings.isNewAppLayoutActivated ? self.createAllChatsCoordinator() : self.createTabBarCoordinator() + masterCoordinator.splitViewMasterPresentableDelegate = self + masterCoordinator.start(with: spaceId) // Create secondary controller let placeholderDetailViewController = self.createPlaceholderDetailsViewController() let detailNavigationController = RiotNavigationController(rootViewController: placeholderDetailViewController) // Setup split view controller - self.splitViewController.viewControllers = [tabBarCoordinator.toPresentable(), detailNavigationController] + self.splitViewController.viewControllers = [masterCoordinator.toPresentable(), detailNavigationController] // Setup detail user indicator presenter let context = SplitViewUserIndicatorPresentationContext( splitViewController: splitViewController, - tabBarCoordinator: tabBarCoordinator, + masterCoordinator: masterCoordinator, detailNavigationController: detailNavigationController ) detailUserIndicatorPresenter = UserIndicatorTypePresenter(presentationContext: context) - self.add(childCoordinator: tabBarCoordinator) + self.add(childCoordinator: masterCoordinator) - self.tabBarCoordinator = tabBarCoordinator - self.masterPresentable = tabBarCoordinator + self.masterCoordinator = masterCoordinator + self.masterPresentable = masterCoordinator self.detailNavigationController = detailNavigationController self.detailNavigationRouter = NavigationRouter(navigationController: detailNavigationController) @@ -126,7 +126,7 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { // Pop to home screen when selecting a new space self.popToHome(animated: true) { // Update tabBarCoordinator selected space - self.tabBarCoordinator?.start(with: spaceId) + self.masterCoordinator?.start(with: spaceId) } } } @@ -145,26 +145,26 @@ final class SplitViewCoordinator: NSObject, SplitViewCoordinatorType { self.resetDetailNavigationController(animated: animated) // Release the current selected item (room/contact/group...). - self.tabBarCoordinator?.releaseSelectedItems() + self.masterCoordinator?.releaseSelectedItems() } func popToHome(animated: Bool, completion: (() -> Void)?) { self.resetDetails(animated: animated) // Force back to the main screen if this is not the one that is displayed - self.tabBarCoordinator?.popToHome(animated: animated, completion: completion) + self.masterCoordinator?.popToHome(animated: animated, completion: completion) } func showErroIndicator(with error: Error) { - tabBarCoordinator?.showErroIndicator(with: error) + masterCoordinator?.showErroIndicator(with: error) } func hideAppStateIndicator() { - tabBarCoordinator?.hideAppStateIndicator() + masterCoordinator?.hideAppStateIndicator() } func showAppStateIndicator(with text: String, icon: UIImage?) { - tabBarCoordinator?.showAppStateIndicator(with: text, icon: icon) + masterCoordinator?.showAppStateIndicator(with: text, icon: icon) } // MARK: - Private methods @@ -347,8 +347,8 @@ extension SplitViewCoordinator: UISplitViewControllerDelegate { } // MARK: - TabBarCoordinatorDelegate -extension SplitViewCoordinator: TabBarCoordinatorDelegate { - func tabBarCoordinatorDidCompleteAuthentication(_ coordinator: TabBarCoordinatorType) { +extension SplitViewCoordinator: SplitViewMasterCoordinatorDelegate { + func splitViewMasterCoordinatorDidCompleteAuthentication(_ coordinator: SplitViewMasterCoordinatorProtocol) { self.delegate?.splitViewCoordinatorDidCompleteAuthentication(self) } } diff --git a/Riot/Modules/SplitView/SplitViewMasterViewControllerProtocol.h b/Riot/Modules/SplitView/SplitViewMasterViewControllerProtocol.h new file mode 100644 index 0000000000..bb87e00ded --- /dev/null +++ b/Riot/Modules/SplitView/SplitViewMasterViewControllerProtocol.h @@ -0,0 +1,112 @@ +// +// 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. +// + +#ifndef SplitViewMasterViewControllerProtocol_h +#define SplitViewMasterViewControllerProtocol_h + +@class RoomNavigationParameters; +@class RoomPreviewNavigationParameters; +@class ScreenPresentationParameters; +@class OnboardingCoordinatorBridgePresenter; + +@protocol SplitViewMasterTabBarViewControllerProtocol + +/// Refresh the missed conversations badges on tab bar icon +- (void)refreshTabBarBadges; + +/// Emulated `UITabBarViewController.selectedViewController` property +@property (nonatomic, readonly, nullable) UIViewController *selectedViewController; + +/// Emulated `UITabBarViewController.tabBar` property +@property (nonatomic, readonly, nullable) UITabBar *tabBar; + +@end + +/// `SplitViewMasterViewControllerProtocol` describe the methods and properties needed by +@protocol SplitViewMasterViewControllerProtocol + +/// Display the default onboarding flow. +- (void)showOnboardingFlow; + +/// Display the onboarding flow configured to log back into a soft logout session. +/// +/// @param softLogoutCredentials the credentials of the soft logout session. +- (void)showSoftLogoutOnboardingFlowWithCredentials:(MXCredentials*)softLogoutCredentials; + +/// Open the room with the provided identifier in a specific matrix session. +/// +/// @param parameters the presentation parameters that contains room information plus display information. +/// @param completion the block to execute at the end of the operation. +- (void)selectRoomWithParameters:(RoomNavigationParameters*)parameters completion:(void (^)(void))completion; + +/// Open the RoomViewController to display the preview of a room that is unknown for the user. +/// This room can come from an email invitation link or a simple link to a room. +/// +/// @param parameters the presentation parameters that contains room preview information plus display information. +/// @param completion the block to execute at the end of the operation. +- (void)selectRoomPreviewWithParameters:(RoomPreviewNavigationParameters*)parameters completion:(void (^)(void))completion; + +/// Open a ContactDetailsViewController to display the information of the provided contact. +/// +/// @param contact contact to be displayed +- (void)selectContact:(MXKContact*)contact; + +/// Open a ContactDetailsViewController to display the information of the provided contact according to the presentation parameters. +/// +/// @param contact contact to be displayed +/// @param presentationParameters the presentation parameters that contains room preview information plus display information. +- (void)selectContact:(MXKContact*)contact withPresentationParameters:(ScreenPresentationParameters*)presentationParameters; + +/// Release the current selected item (if any). +- (void)releaseSelectedItem; + +/// The current number of rooms with missed notifications, including the invites. +- (NSUInteger)missedDiscussionsCount; + +/// The current number of rooms with unread highlighted messages. +- (NSUInteger)missedHighlightDiscussionsCount; + +/// Verify the current device if needed. +/// +/// @param session the matrix session. +- (void)presentVerifyCurrentSessionAlertIfNeededWithSession:(MXSession*)session; + +/// Verify others device if needed. +/// +/// @param session the matrix session. +- (void)presentReviewUnverifiedSessionsAlertIfNeededWithSession:(MXSession*)session; + +/// Reference to the current onboarding flow. It is always nil unless the flow is being presented. +@property (nonatomic, readonly) OnboardingCoordinatorBridgePresenter *onboardingCoordinatorBridgePresenter; + +/// Reference on the currently selected room +@property (nonatomic, readonly) NSString *selectedRoomId; +/// Reference on the currently selected event +@property (nonatomic, readonly) NSString *selectedEventId; +/// Reference on the currently selected room session +@property (nonatomic, readonly) MXSession *selectedRoomSession; +/// Reference on the currently selected room preview data +@property (nonatomic, readonly) RoomPreviewData *selectedRoomPreviewData; + +/// Reference on the currently selected contact +@property (nonatomic, readonly) MXKContact *selectedContact; + +/// `true` while the onboarding flow is displayed +@property (nonatomic, readonly) BOOL isOnboardingInProgress; + +@end + +#endif /* SplitViewMasterViewControllerProtocol_h */ diff --git a/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift b/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift index 65197831a1..bc82225172 100644 --- a/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift +++ b/Riot/Modules/SplitView/SplitViewUserIndicatorPresentationContext.swift @@ -19,28 +19,28 @@ import CommonKit class SplitViewUserIndicatorPresentationContext: UserIndicatorPresentationContext { private weak var splitViewController: UISplitViewController? - private weak var tabBarCoordinator: TabBarCoordinatorType? + private weak var masterCoordinator: SplitViewMasterCoordinatorProtocol? private weak var detailNavigationController: UINavigationController? init( splitViewController: UISplitViewController, - tabBarCoordinator: TabBarCoordinatorType, + masterCoordinator: SplitViewMasterCoordinatorProtocol, detailNavigationController: UINavigationController ) { self.splitViewController = splitViewController - self.tabBarCoordinator = tabBarCoordinator + self.masterCoordinator = masterCoordinator self.detailNavigationController = detailNavigationController } var indicatorPresentingViewController: UIViewController? { guard let splitViewController = splitViewController, - let tabBarCoordinator = tabBarCoordinator, + let masterCoordinator = masterCoordinator, let detailNavigationController = detailNavigationController else { MXLog.debug("[SplitViewCoordinator]: Missing tab bar or detail coordinator, cannot update user indicator presenter") return nil } - return splitViewController.isCollapsed ? tabBarCoordinator.toPresentable() : detailNavigationController + return splitViewController.isCollapsed ? masterCoordinator.toPresentable() : detailNavigationController } } diff --git a/Riot/Modules/TabBar/MasterTabBarController.h b/Riot/Modules/TabBar/MasterTabBarController.h index e948bb3017..f0d0df2ce9 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.h +++ b/Riot/Modules/TabBar/MasterTabBarController.h @@ -22,6 +22,7 @@ #import "FavouritesViewController.h" #import "PeopleViewController.h" #import "RoomsViewController.h" +#import "SplitViewMasterViewControllerProtocol.h" #define TABBAR_HOME_INDEX 0 #define TABBAR_FAVOURITES_INDEX 1 @@ -42,7 +43,7 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { @class ScreenPresentationParameters; @class OnboardingCoordinatorBridgePresenter; -@interface MasterTabBarController : UITabBarController +@interface MasterTabBarController : UITabBarController // UITabBarController already have a `delegate` property @property (weak, nonatomic) id masterTabBarDelegate; @@ -55,71 +56,6 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { // Remove a matrix session. - (void)removeMatrixSession:(MXSession*)mxSession; -/** - Display the default onboarding flow. - */ -- (void)showOnboardingFlow; - -/** - Display the onboarding flow configured to log back into a soft logout session. - - @param softLogoutCredentials the credentials of the soft logout session. - */ -- (void)showSoftLogoutOnboardingFlowWithCredentials:(MXCredentials*)softLogoutCredentials; - -/// Open the room with the provided identifier in a specific matrix session. -/// @param parameters the presentation parameters that contains room information plus display information. -/// @param completion the block to execute at the end of the operation. -- (void)selectRoomWithParameters:(RoomNavigationParameters*)parameters completion:(void (^)(void))completion; - -/// Open the RoomViewController to display the preview of a room that is unknown for the user. -/// This room can come from an email invitation link or a simple link to a room. -/// @param parameters the presentation parameters that contains room preview information plus display information. -/// @param completion the block to execute at the end of the operation. -- (void)selectRoomPreviewWithParameters:(RoomPreviewNavigationParameters*)parameters completion:(void (^)(void))completion; - -/** - Open a ContactDetailsViewController to display the information of the provided contact. - */ -- (void)selectContact:(MXKContact*)contact; - -- (void)selectContact:(MXKContact*)contact withPresentationParameters:(ScreenPresentationParameters*)presentationParameters; - -/** - Release the current selected item (if any). - */ -- (void)releaseSelectedItem; - -/** - The current number of rooms with missed notifications, including the invites. - */ -- (NSUInteger)missedDiscussionsCount; - -/** - The current number of rooms with unread highlighted messages. - */ -- (NSUInteger)missedHighlightDiscussionsCount; - -/** - Refresh the missed conversations badges on tab bar icon - */ -- (void)refreshTabBarBadges; - -/** - Verify the current device if needed. - - @param session the matrix session. - */ -- (void)presentVerifyCurrentSessionAlertIfNeededWithSession:(MXSession*)session; - -/** - Verify others device if needed. - - @param session the matrix session. - */ -- (void)presentReviewUnverifiedSessionsAlertIfNeededWithSession:(MXSession*)session; - - /// Filter rooms for each tab data source with the given room parent id. /// It should keep rooms having an ancestor with `roomParentId` as parent id. /// @param roomParentId The room parent id used to filter rooms. @@ -127,27 +63,11 @@ typedef NS_ENUM(NSUInteger, MasterTabBarIndex) { - (void)filterRoomsWithParentId:(NSString*)roomParentId inMatrixSession:(MXSession*)mxSession; -// Reference to the current onboarding flow. It is always nil unless the flow is being presented. -@property (nonatomic, readonly) OnboardingCoordinatorBridgePresenter *onboardingCoordinatorBridgePresenter; - @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 -@property (nonatomic, readonly) NSString *selectedRoomId; -@property (nonatomic, readonly) NSString *selectedEventId; -@property (nonatomic, readonly) MXSession *selectedRoomSession; -@property (nonatomic, readonly) RoomPreviewData *selectedRoomPreviewData; - -// References on the currently selected contact -@property (nonatomic, readonly) MXKContact *selectedContact; - -// YES while the onboarding flow is displayed -@property (nonatomic, readonly) BOOL isOnboardingInProgress; - // Set tab bar item controllers - (void)updateViewControllers:(NSArray*)viewControllers; diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 96cc0880fa..fe0311a9ea 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -65,6 +65,7 @@ @interface MasterTabBarController () @end @implementation MasterTabBarController +@synthesize onboardingCoordinatorBridgePresenter, selectedRoomId, selectedEventId, selectedRoomSession, selectedRoomPreviewData, selectedContact, isOnboardingInProgress; #pragma mark - Properties override @@ -530,9 +531,9 @@ - (void)selectRoomWithParameters:(RoomNavigationParameters*)paramaters completio { [self releaseSelectedItem]; - _selectedRoomId = paramaters.roomId; - _selectedEventId = paramaters.eventId; - _selectedRoomSession = paramaters.mxSession; + selectedRoomId = paramaters.roomId; + selectedEventId = paramaters.eventId; + selectedRoomSession = paramaters.mxSession; [self.masterTabBarDelegate masterTabBarController:self didSelectRoomWithParameters:paramaters completion:completion]; @@ -545,9 +546,9 @@ - (void)selectRoomPreviewWithParameters:(RoomPreviewNavigationParameters*)parame RoomPreviewData *roomPreviewData = parameters.previewData; - _selectedRoomPreviewData = roomPreviewData; - _selectedRoomId = roomPreviewData.roomId; - _selectedRoomSession = roomPreviewData.mxSession; + selectedRoomPreviewData = roomPreviewData; + selectedRoomId = roomPreviewData.roomId; + selectedRoomSession = roomPreviewData.mxSession; [self.masterTabBarDelegate masterTabBarController:self didSelectRoomPreviewWithParameters:parameters completion:completion]; @@ -565,7 +566,7 @@ - (void)selectContact:(MXKContact*)contact withPresentationParameters:(ScreenPre { [self releaseSelectedItem]; - _selectedContact = contact; + selectedContact = contact; [self.masterTabBarDelegate masterTabBarController:self didSelectContact:contact withPresentationParameters:presentationParameters]; @@ -574,12 +575,12 @@ - (void)selectContact:(MXKContact*)contact withPresentationParameters:(ScreenPre - (void)releaseSelectedItem { - _selectedRoomId = nil; - _selectedEventId = nil; - _selectedRoomSession = nil; - _selectedRoomPreviewData = nil; + selectedRoomId = nil; + selectedEventId = nil; + selectedRoomSession = nil; + selectedRoomPreviewData = nil; - _selectedContact = nil; + selectedContact = nil; } - (NSUInteger)missedDiscussionsCount diff --git a/Riot/Modules/TabBar/TabBarCoordinatorType.swift b/Riot/Modules/TabBar/SplitViewMasterCoordinatorProtocol.swift similarity index 76% rename from Riot/Modules/TabBar/TabBarCoordinatorType.swift rename to Riot/Modules/TabBar/SplitViewMasterCoordinatorProtocol.swift index 2d6d290b0b..d83d6fc06a 100644 --- a/Riot/Modules/TabBar/TabBarCoordinatorType.swift +++ b/Riot/Modules/TabBar/SplitViewMasterCoordinatorProtocol.swift @@ -18,15 +18,15 @@ import Foundation -protocol TabBarCoordinatorDelegate: AnyObject { - // TODO: Remove this method, authentication should not be handled by TabBarCoordinator - func tabBarCoordinatorDidCompleteAuthentication(_ coordinator: TabBarCoordinatorType) +protocol SplitViewMasterCoordinatorDelegate: AnyObject { + // TODO: Remove this method, authentication should not be handled by SplitViewMasterCoordinator + func splitViewMasterCoordinatorDidCompleteAuthentication(_ coordinator: SplitViewMasterCoordinatorProtocol) } -/// `TabBarCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. -protocol TabBarCoordinatorType: Coordinator, SplitViewMasterPresentable { +/// `SplitViewMasterCoordinatorProtocol` is a protocol describing a Coordinator that handle the master view controller of the `UISplitViewController` +protocol SplitViewMasterCoordinatorProtocol: Coordinator, SplitViewMasterPresentable { - var delegate: TabBarCoordinatorDelegate? { get } + var delegate: SplitViewMasterCoordinatorDelegate? { get } /// Start coordinator by selecting a Space. /// - Parameter spaceId: The id of the Space to use. diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 935d21a43f..33843d4382 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -23,7 +23,7 @@ import CommonKit import MatrixSDK @objcMembers -final class TabBarCoordinator: NSObject, TabBarCoordinatorType { +final class TabBarCoordinator: NSObject, SplitViewMasterCoordinatorProtocol { // MARK: - Properties @@ -77,7 +77,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { // Must be used only internally var childCoordinators: [Coordinator] = [] - weak var delegate: TabBarCoordinatorDelegate? + weak var delegate: SplitViewMasterCoordinatorDelegate? weak var splitViewMasterPresentableDelegate: SplitViewMasterPresentableDelegate? @@ -933,7 +933,7 @@ extension TabBarCoordinator: MasterTabBarControllerDelegate { } func masterTabBarControllerDidCompleteAuthentication(_ masterTabBarController: MasterTabBarController!) { - self.delegate?.tabBarCoordinatorDidCompleteAuthentication(self) + self.delegate?.splitViewMasterCoordinatorDidCompleteAuthentication(self) } func masterTabBarController(_ masterTabBarController: MasterTabBarController!, didSelectRoomWithId roomId: String!, andEventId eventId: String!, inMatrixSession matrixSession: MXSession!, completion: (() -> Void)!) {