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