From fcc1a8a40431121fb61fdfe1dd813400a3a02b9e 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 228751d31..c98dd254e 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1714,7 +1714,10 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou - (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 3ff5775d0..92ddc408d 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -373,8 +373,6 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro - (void)refreshRecentsTable { - MXLogDebug(@"[RecentsViewController]: Refreshing recents table view") - if (!self.recentsUpdateEnabled) { isRefreshNeeded = YES; @@ -384,7 +382,11 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro 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 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [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 *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro - (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 6cd41134f..c09822c15 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -88,7 +88,7 @@ { self.lastEventDescription.text = roomCellData.lastEventTextMessage; } - + self.unsentImageView.hidden = roomCellData.roomSummary.sentStatus == MXRoomSummarySentStatusOk; self.lastEventDecriptionLabelTrailingConstraint.constant = self.unsentImageView.hidden ? 10 : 30; @@ -96,17 +96,17 @@ 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 @@ 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 000000000..1fa4f376c --- /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 bad683d38..7d99beabf 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 20803b426..83b4849ab 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 ead8dc802..e912d491e 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 0dbb0ef8a..2cfe23047 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 c46e9d577..684a40ec3 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -50,7 +50,6 @@ @property (nonatomic, strong) MXThrottler *collectionViewPaginationThrottler; @property(nonatomic) SpaceMembersCoordinatorBridgePresenter *spaceMembersCoordinatorBridgePresenter; -@property (nonatomic, strong) MXThrottler *tableViewPaginationThrottler; @end @@ -73,7 +72,6 @@ self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenHome]; self.collectionViewPaginationThrottler = [[MXThrottler alloc] initWithMinimumDelay:0.1]; - self.tableViewPaginationThrottler = [[MXThrottler alloc] initWithMinimumDelay:0.1]; } - (void)viewDidLoad @@ -109,9 +107,12 @@ { [super viewWillAppear:animated]; - [ThemeService.shared.theme applyStyleOnNavigationBar:[AppDelegate theDelegate].masterTabBarController.navigationController.navigationBar]; + if (!BuildSettings.isNewAppLayoutActivated) + { + [ThemeService.shared.theme applyStyleOnNavigationBar:[AppDelegate theDelegate].masterTabBarController.navigationController.navigationBar]; - [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; + [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; + } if (recentsDataSource.recentsDataSourceMode != self.recentsDataSourceMode) { @@ -585,34 +586,6 @@ } } -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - RecentsDataSourceSectionType sectionType = [recentsDataSource.sections sectionTypeForSectionIndex:indexPath.section]; - if (sectionType != RecentsDataSourceSectionTypeAllChats) - { - return; - } - - if ([super respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)]) - { - [super tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath]; - } - - [self.tableViewPaginationThrottler throttle:^{ - NSInteger section = indexPath.section; - if (tableView.numberOfSections <= section) - { - return; - } - - NSInteger numberOfRowsInSection = [tableView numberOfRowsInSection:section]; - if (indexPath.row == numberOfRowsInSection - 1) - { - [self->recentsDataSource paginateInSection:section]; - } - }]; -} - #pragma mark - UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section @@ -691,19 +664,21 @@ - (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 9a2a332ac..000000000 --- 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 82c611040..592d20b4b 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 e007b8595..a720744cc 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -221,6 +221,11 @@ { [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 @@ { _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 7d3da462f..fb66af2e7 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -456,7 +456,7 @@ static CGSize kThreadListBarButtonItemImageSize; 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 @@ static CGSize kThreadListBarButtonItemImageSize; } [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 a3de10ee5..f1d94e043 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 d379e8a10..65197831a 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 711e1e1cf..935d21a43 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 7eacc6f91..d36cedbd4 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 000000000..78f40969a --- /dev/null +++ b/changelog.d/6513.bugfix @@ -0,0 +1 @@ +Glitchy room list header when scrolling From 5f868b2851523fee859453348bd5e2a5984d95c7 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 000000000..ee562a1c6 --- /dev/null +++ b/changelog.d/6537.bugfix @@ -0,0 +1 @@ +Performance issues with new App Layout From af49d3f6ed0ab50f72c34f258fae4ea5c16a81bf 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 b4ed78852..40faf96b4 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 1fa4f376c..052572736 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 7d99beabf..27901c266 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 fb66af2e7..bb0767c47 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -456,7 +456,7 @@ static CGSize kThreadListBarButtonItemImageSize; 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 f1d94e043..5365db16b 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 000000000..bb87e00de --- /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 65197831a..bc8222517 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 e948bb301..f0d0df2ce 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 96cc0880f..fe0311a9e 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -65,6 +65,7 @@ @end @implementation MasterTabBarController +@synthesize onboardingCoordinatorBridgePresenter, selectedRoomId, selectedEventId, selectedRoomSession, selectedRoomPreviewData, selectedContact, isOnboardingInProgress; #pragma mark - Properties override @@ -530,9 +531,9 @@ { [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 @@ 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 @@ { [self releaseSelectedItem]; - _selectedContact = contact; + selectedContact = contact; [self.masterTabBarDelegate masterTabBarController:self didSelectContact:contact withPresentationParameters:presentationParameters]; @@ -574,12 +575,12 @@ - (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 2d6d290b0..d83d6fc06 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 935d21a43..33843d438 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)!) {