Join room loading indicators

This commit is contained in:
Andy Uhnak 2022-03-23 16:26:44 +00:00
parent 4e67b0b406
commit 92b31a0f01
20 changed files with 287 additions and 265 deletions

View file

@ -68,7 +68,8 @@ public class UserIndicator {
/// Cancel the indicator, triggering any dismissal action / animation
///
/// Note: clients can call this method directly, if they have access to the `UserIndicator`.
/// Note: clients can call this method directly, if they have access to the `UserIndicator`. Alternatively
/// deallocating the `UserIndicator` will call `cancel` automatically.
/// Once cancelled, `UserIndicatorQueue` will automatically start the next `UserIndicator` in the queue.
public func cancel() {
complete()

View file

@ -59,6 +59,7 @@
"send_to" = "Send to %@";
"close" = "Close";
"skip" = "Skip";
"joining" = "Joining";
"joined" = "Joined";
"switch" = "Switch";
"more" = "More";

View file

@ -2219,6 +2219,10 @@ public class VectorL10n: NSObject {
public static var joined: String {
return VectorL10n.tr("Vector", "joined")
}
/// Joining
public static var joining: String {
return VectorL10n.tr("Vector", "joining")
}
/// Done
public static var keyBackupRecoverDoneAction: String {
return VectorL10n.tr("Vector", "key_backup_recover_done_action")

View file

@ -0,0 +1,27 @@
//
// 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 UserIndicatorCancel_h
#define UserIndicatorCancel_h
/**
Callback function to cancel a `UserIndicator` without needing a direct reference to the object
Note: the function is defined in Objective-C (instead of Swift) to be accessible by both languages.
*/
typedef void (^UserIndicatorCancel)(void);
#endif /* UserIndicatorCancel_h */

View file

@ -0,0 +1,54 @@
//
// 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
/// An abstraction on top of `UserIndicatorTypePresenterProtocol` which manages and stores the individual user indicators.
/// When used to present an indicator the `UserIndicatorStore` will instead returns a simple callback function to the clients
/// letting them cancel the indicators without worrying about memory.
@objc final class UserIndicatorStore: NSObject {
private let presenter: UserIndicatorTypePresenterProtocol
private var indicators: [UserIndicator]
init(presenter: UserIndicatorTypePresenterProtocol) {
self.presenter = presenter
self.indicators = []
}
/// Present a new type of user indicator, such as loading spinner or success message.
/// To remove an indicator, call the returned `UserIndicatorCancel` function
func present(type: UserIndicatorType) -> UserIndicatorCancel {
let indicator = presenter.present(type)
indicators.append(indicator)
return {
indicator.cancel()
}
}
/// Present a new type of user indicator, such as loading spinner or success message.
/// To remove an indicator, call the returned `UserIndicatorCancel` function
///
/// Note: This is a convenience function callable by objective-c code
@objc func presentLoading(label: String, isInteractionBlocking: Bool) -> UserIndicatorCancel {
present(
type: .loading(
label: label,
isInteractionBlocking: isInteractionBlocking
)
)
}
}

View file

@ -15,3 +15,4 @@
#import "MXKRoomInputToolbarView.h"
#import "MXKImageView.h"
#import "MXKRoomBubbleCellData.h"
#import "UserIndicatorCancel.h"

View file

@ -27,6 +27,8 @@
#import "MXKAttachmentsViewController.h"
#import "MXKAttachmentAnimator.h"
@class UserIndicatorStore;
typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
MXKRoomViewControllerJoinRoomResultSuccess,
MXKRoomViewControllerJoinRoomResultFailureRoomEmpty,
@ -198,6 +200,12 @@ typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) {
*/
@property NSTimeInterval resizeComposerAnimationDuration;
/**
A store of user indicators that lets the room present and dismiss indicators without
worrying about the presentation context or memory management.
*/
@property (strong, nonatomic) UserIndicatorStore *userIndicatorStore;
/**
This object is defined when the displayed room is left. It is added into the bubbles table header.
This label is used to display the reason why the room has been left.

View file

@ -859,12 +859,11 @@
return;
}
[self startActivityIndicator];
UserIndicatorCancel cancelIndicator = [self.userIndicatorStore presentLoadingWithLabel:[VectorL10n joining] isInteractionBlocking:YES];
joinRoomRequest = [roomDataSource.room join:^{
self->joinRoomRequest = nil;
[self stopActivityIndicator];
cancelIndicator();
[self triggerInitialBackPagination];
@ -874,6 +873,7 @@
}
} failure:^(NSError *error) {
cancelIndicator();
MXLogDebug(@"[MXKRoomVC] Failed to join room (%@)", self->roomDataSource.room.summary.displayname);
[self processRoomJoinFailureWithError:error completion:completion];
}];
@ -894,12 +894,11 @@
return;
}
[self startActivityIndicator];
UserIndicatorCancel cancelIndicator = [self.userIndicatorStore presentLoadingWithLabel:[VectorL10n joining] isInteractionBlocking:YES];
void (^success)(MXRoom *room) = ^(MXRoom *room) {
self->joinRoomRequest = nil;
[self stopActivityIndicator];
cancelIndicator();
MXWeakify(self);
@ -921,6 +920,7 @@
};
void (^failure)(NSError *error) = ^(NSError *error) {
cancelIndicator();
MXLogDebug(@"[MXKRoomVC] Failed to join room (%@)", roomIdOrAlias);
[self processRoomJoinFailureWithError:error completion:completion];
};

View file

@ -28,9 +28,9 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
private let parameters: RoomCoordinatorParameters
private let roomViewController: RoomViewController
private let activityIndicatorPresenter: ActivityIndicatorPresenterType
private let userIndicatorStore: UserIndicatorStore
private var selectedEventId: String?
private var loadingIndicator: UserIndicator?
private var loadingCancel: UserIndicatorCancel?
private var roomDataSourceManager: MXKRoomDataSourceManager {
return MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
@ -73,6 +73,7 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
init(parameters: RoomCoordinatorParameters) {
self.parameters = parameters
self.selectedEventId = parameters.eventId
self.userIndicatorStore = UserIndicatorStore(presenter: parameters.userIndicatorPresenter)
if let threadId = parameters.threadId {
self.roomViewController = ThreadViewController.instantiate(withThreadId: threadId,
@ -80,8 +81,8 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
} else {
self.roomViewController = RoomViewController.instantiate(with: parameters.displayConfiguration)
}
self.roomViewController.userIndicatorStore = userIndicatorStore
self.roomViewController.showSettingsInitially = parameters.showSettingsInitially
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
self.roomViewController.parentSpaceId = parameters.parentSpaceId
@ -319,20 +320,21 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
}
private func startLoading() {
if let presenter = parameters.userIndicatorPresenter {
if loadingIndicator == nil {
// The `RoomViewController` does not currently ensure that `startLoading` is matched by corresponding `stopLoading` and may
// thus trigger start of loading multiple times. To solve for this we will hold onto the cancellation reference of the
// last loading request, and if one already exists, we will not present a new indicator.
guard loadingCancel == nil else {
return
}
MXLog.debug("[RoomCoordinator] Present loading indicator in a room: \(roomId ?? "unknown")")
loadingIndicator = presenter.present(.loading(label: VectorL10n.homeSyncing, isInteractionBlocking: false))
}
} else {
activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: true)
}
loadingCancel = userIndicatorStore.present(type: .loading(label: VectorL10n.homeSyncing, isInteractionBlocking: false))
}
private func stopLoading() {
MXLog.debug("[RoomCoordinator] Dismiss loading indicator in a room: \(roomId ?? "unknown")")
loadingIndicator = nil
activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
loadingCancel?()
loadingCancel = nil
}
}
@ -448,10 +450,6 @@ extension RoomCoordinator: RoomViewControllerDelegate {
startEditPollCoordinator(startEvent: startEvent)
}
func roomViewControllerCanDelegateUserIndicators(_ roomViewController: RoomViewController) -> Bool {
return parameters.userIndicatorPresenter != nil
}
func roomViewControllerDidStartLoading(_ roomViewController: RoomViewController) {
startLoading()
}
@ -467,4 +465,18 @@ extension RoomCoordinator: RoomViewControllerDelegate {
func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController) {
// TODO:
}
func threadsCoordinator(for roomViewController: RoomViewController, threadId: String?) -> ThreadsCoordinatorBridgePresenter? {
guard let session = mxSession, let roomId = roomId else {
MXLog.error("[RoomCoordinator] Cannot create threads coordinator for room \(roomId ?? "")")
return nil
}
return ThreadsCoordinatorBridgePresenter(
session: session,
roomId: roomId,
threadId: threadId,
userIndicatorPresenter: parameters.userIndicatorPresenter
)
}
}

View file

@ -1,198 +0,0 @@
//
// Copyright 2021 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
@objc protocol RoomCoordinatorBridgePresenterDelegate {
func roomCoordinatorBridgePresenterDidLeaveRoom(_ bridgePresenter: RoomCoordinatorBridgePresenter)
func roomCoordinatorBridgePresenterDidCancelRoomPreview(_ bridgePresenter: RoomCoordinatorBridgePresenter)
func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter,
didSelectRoomWithId roomId: String,
eventId: String?)
func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didReplaceRoomWithReplacementId roomId: String)
func roomCoordinatorBridgePresenterDidDismissInteractively(_ bridgePresenter: RoomCoordinatorBridgePresenter)
}
@objcMembers
class RoomCoordinatorBridgePresenterParameters: NSObject {
/// The matrix session in which the room should be available.
let session: MXSession
/// The room identifier
let roomId: String
/// The identifier of the parent space. `nil` for home space
let parentSpaceId: String?
/// If not nil, the room will be opened on this event.
let eventId: String?
/// If not nil, specified thread will be opened.
let threadId: String?
/// Display configuration for the room
let displayConfiguration: RoomDisplayConfiguration
/// The data for the room preview.
let previewData: RoomPreviewData?
/// If `true`, the room settings screen will be initially displayed. Default `false`
let showSettingsInitially: Bool
init(session: MXSession,
roomId: String,
parentSpaceId: String?,
eventId: String?,
threadId: String?,
displayConfiguration: RoomDisplayConfiguration,
previewData: RoomPreviewData?,
showSettingsInitially: Bool) {
self.session = session
self.roomId = roomId
self.parentSpaceId = parentSpaceId
self.eventId = eventId
self.threadId = threadId
self.displayConfiguration = displayConfiguration
self.previewData = previewData
self.showSettingsInitially = showSettingsInitially
}
}
/// RoomCoordinatorBridgePresenter enables to start RoomCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
/// **WARNING**: This class breaks the Coordinator abstraction and it has been introduced for **Objective-C compatibility only** (mainly for integration in legacy view controllers). Each bridge should be removed
/// once the underlying Coordinator has been integrated by another Coordinator.
@objcMembers
final class RoomCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let bridgeParameters: RoomCoordinatorBridgePresenterParameters
private var coordinator: RoomCoordinator?
private var navigationType: NavigationType = .present
private enum NavigationType {
case present
case push
}
// MARK: Public
weak var delegate: RoomCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(parameters: RoomCoordinatorBridgePresenterParameters) {
self.bridgeParameters = parameters
super.init()
}
// MARK: - Public
func present(from viewController: UIViewController, animated: Bool) {
let coordinator = self.createRoomCoordinator(parentSpaceId: bridgeParameters.parentSpaceId)
coordinator.delegate = self
let presentable = coordinator.toPresentable()
presentable.modalPresentationStyle = .formSheet
viewController.present(presentable, animated: animated, completion: nil)
coordinator.start()
self.coordinator = coordinator
self.navigationType = .present
}
func push(from navigationController: UINavigationController, animated: Bool) {
let navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
let coordinator = self.createRoomCoordinator(with: navigationRouter, parentSpaceId: bridgeParameters.parentSpaceId)
coordinator.delegate = self
coordinator.start() // Will trigger view controller push
self.coordinator = coordinator
self.navigationType = .push
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
switch navigationType {
case .present:
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
completion?()
}
case .push:
guard let navigationController = coordinator.toPresentable().navigationController else {
return
}
navigationController.popViewController(animated: animated)
self.coordinator = nil
completion?()
}
}
// MARK: - Private
private func createRoomCoordinator(with navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController()), parentSpaceId: String?) -> RoomCoordinator {
let coordinatorParameters: RoomCoordinatorParameters
if let previewData = self.bridgeParameters.previewData {
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, parentSpaceId: parentSpaceId, previewData: previewData)
} else {
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter,
session: self.bridgeParameters.session,
parentSpaceId: parentSpaceId,
roomId: self.bridgeParameters.roomId,
eventId: self.bridgeParameters.eventId,
threadId: self.bridgeParameters.threadId,
showSettingsInitially: self.bridgeParameters.showSettingsInitially,
displayConfiguration: self.bridgeParameters.displayConfiguration)
}
return RoomCoordinator(parameters: coordinatorParameters)
}
}
// MARK: - RoomNotificationSettingsCoordinatorDelegate
extension RoomCoordinatorBridgePresenter: RoomCoordinatorDelegate {
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String, eventId: String?) {
self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId, eventId: eventId)
}
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didReplaceRoomWithReplacementId roomId: String) {
self.delegate?.roomCoordinatorBridgePresenter(self, didReplaceRoomWithReplacementId: roomId)
}
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidLeaveRoom(self)
}
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidCancelRoomPreview(self)
}
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidDismissInteractively(self)
}
}

View file

@ -29,7 +29,7 @@ struct RoomCoordinatorParameters {
let navigationRouterStore: NavigationRouterStoreProtocol?
/// Presenter for displaying loading indicators, success messages and other user indicators
let userIndicatorPresenter: UserIndicatorTypePresenterProtocol?
let userIndicatorPresenter: UserIndicatorTypePresenterProtocol
/// The matrix session in which the room should be available.
let session: MXSession
@ -59,7 +59,7 @@ struct RoomCoordinatorParameters {
private init(navigationRouter: NavigationRouterType?,
navigationRouterStore: NavigationRouterStoreProtocol?,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol?,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol,
session: MXSession,
roomId: String,
parentSpaceId: String?,
@ -84,7 +84,7 @@ struct RoomCoordinatorParameters {
/// Init to present a joined room
init(navigationRouter: NavigationRouterType? = nil,
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol? = nil,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol,
session: MXSession,
parentSpaceId: String?,
roomId: String,
@ -109,12 +109,13 @@ struct RoomCoordinatorParameters {
/// Init to present a room preview
init(navigationRouter: NavigationRouterType? = nil,
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol,
parentSpaceId: String?,
previewData: RoomPreviewData) {
self.init(navigationRouter: navigationRouter,
navigationRouterStore: navigationRouterStore,
userIndicatorPresenter: nil,
userIndicatorPresenter: userIndicatorPresenter,
session: previewData.mxSession,
roomId: previewData.roomId,
parentSpaceId: parentSpaceId,

View file

@ -31,6 +31,7 @@
@class UniversalLinkParameters;
@protocol RoomViewControllerDelegate;
@class RoomDisplayConfiguration;
@class ThreadsCoordinatorBridgePresenter;
NS_ASSUME_NONNULL_BEGIN
@ -253,14 +254,6 @@ canEditPollWithEventIdentifier:(NSString *)eventIdentifier;
- (void)roomViewController:(RoomViewController *)roomViewController
didRequestEditForPollWithStartEvent:(MXEvent *)startEvent;
/**
Checks whether the delegate supports handling of activity indicators
Note: This is a transition API whilst `RoomViewController` contains legacy activity indicators
as well as using a newer user interaction presenters.
*/
- (BOOL)roomViewControllerCanDelegateUserIndicators:(RoomViewController *)roomViewController;
/**
Indicate to the delegate that loading should start
@ -283,6 +276,9 @@ didRequestEditForPollWithStartEvent:(MXEvent *)startEvent;
/// User tap live location sharing banner
- (void)roomViewControllerDidTapLiveLocationSharingBanner:(RoomViewController *)roomViewController;
/// Request a threads coordinator for a given threadId, used to open a thread from within a room.
- (nullable ThreadsCoordinatorBridgePresenter *)threadsCoordinatorForRoomViewController:(RoomViewController *)roomViewController threadId:(nullable NSString *)threadId;
@end
NS_ASSUME_NONNULL_END

View file

@ -969,17 +969,13 @@ static CGSize kThreadListBarButtonItemImageSize;
#pragma mark - Loading indicators
- (BOOL)providesCustomActivityIndicator {
return [self.delegate roomViewControllerCanDelegateUserIndicators:self];
return YES;
}
// Override of a legacy method to determine whether to use a newer implementation instead.
// Will be removed in the future https://github.com/vector-im/element-ios/issues/5608
- (void)startActivityIndicator {
if ([self providesCustomActivityIndicator]) {
[self.delegate roomViewControllerDidStartLoading:self];
} else {
[super startActivityIndicator];
}
}
// Override of a legacy method to determine whether to use a newer implementation instead.
@ -992,16 +988,12 @@ static CGSize kThreadListBarButtonItemImageSize;
[MXSDKOptions.sharedInstance.profiler stopMeasuringTaskWithProfile:notificationTaskProfile];
notificationTaskProfile = nil;
}
if ([self providesCustomActivityIndicator]) {
// The legacy super implementation of `stopActivityIndicator` contains a number of checks grouped under `canStopActivityIndicator`
// to determine whether the indicator can be stopped or not (and the method should thus rather be called `stopActivityIndicatorIfPossible`).
// Since the newer indicators are not calling super implementation, the check for `canStopActivityIndicator` has to be performed manually.
if ([self canStopActivityIndicator]) {
[self stopLoadingUserIndicator];
}
} else {
[super stopActivityIndicator];
}
}
- (void)stopLoadingUserIndicator
@ -4733,9 +4725,7 @@ static CGSize kThreadListBarButtonItemImageSize;
- (IBAction)onThreadListTapped:(id)sender
{
self.threadsBridgePresenter = [[ThreadsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession
roomId:self.roomDataSource.roomId
threadId:nil];
self.threadsBridgePresenter = [self.delegate threadsCoordinatorForRoomViewController:self threadId:nil];
self.threadsBridgePresenter.delegate = self;
[self.threadsBridgePresenter pushFrom:self.navigationController animated:YES];
@ -6796,9 +6786,7 @@ static CGSize kThreadListBarButtonItemImageSize;
self.threadsBridgePresenter = nil;
}
self.threadsBridgePresenter = [[ThreadsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession
roomId:self.roomDataSource.roomId
threadId:threadId];
self.threadsBridgePresenter = [self.delegate threadsCoordinatorForRoomViewController:self threadId:threadId];
self.threadsBridgePresenter.delegate = self;
[self.threadsBridgePresenter pushFrom:self.navigationController animated:YES];
}

View file

@ -462,10 +462,6 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate {
return TimelinePollProvider.shared.timelinePollCoordinatorForEventIdentifier(eventIdentifier)?.canEndPoll() ?? false
}
func roomViewControllerCanDelegateUserIndicators(_ roomViewController: RoomViewController) -> Bool {
return false
}
func roomViewControllerDidStartLoading(_ roomViewController: RoomViewController) {
}
@ -481,6 +477,20 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate {
func roomViewControllerDidStopLiveLocationSharing(_ roomViewController: RoomViewController) {
// TODO:
}
func threadsCoordinator(for roomViewController: RoomViewController, threadId: String?) -> ThreadsCoordinatorBridgePresenter? {
guard let roomId = roomViewController.roomPreviewData?.roomId else {
MXLog.error("[ExploreRoomCoordinator] Cannot create threads coordinator for room")
return nil
}
return ThreadsCoordinatorBridgePresenter(
session: session,
roomId: roomId,
threadId: threadId,
userIndicatorPresenter: UserIndicatorTypePresenter(presentingViewController: toPresentable())
)
}
}
// MARK: - ContactsPickerCoordinatorDelegate

View file

@ -55,6 +55,14 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
return self.navigationRouter.modules.last is MasterTabBarController
}
private var detailUserIndicatorPresenter: UserIndicatorTypePresenterProtocol {
guard let presenter = splitViewMasterPresentableDelegate?.detailUserIndicatorPresenter else {
MXLog.debug("[TabBarCoordinator]: Missing detaul user indicator presenter")
return UserIndicatorTypePresenter(presentingViewController: toPresentable())
}
return presenter
}
private var indicators = [UserIndicator]()
// MARK: Public
@ -415,8 +423,9 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
displayConfig = .default
}
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared,
userIndicatorPresenter: splitViewMasterPresentableDelegate?.detailUserIndicatorPresenter,
userIndicatorPresenter: detailUserIndicatorPresenter,
session: roomNavigationParameters.mxSession,
parentSpaceId: self.currentSpaceId,
roomId: roomNavigationParameters.roomId,
@ -435,7 +444,13 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
// 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, session: matrixSession, parentSpaceId: self.currentSpaceId, roomId: roomId, eventId: eventId, showSettingsInitially: false)
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)
}
@ -445,7 +460,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
// 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, parentSpaceId: self.currentSpaceId, previewData: previewData)
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared,
userIndicatorPresenter: detailUserIndicatorPresenter,
parentSpaceId: self.currentSpaceId,
previewData: previewData)
self.showRoom(with: roomCoordinatorParameters)
}
@ -453,7 +471,9 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
private func showRoomPreview(withNavigationParameters roomPreviewNavigationParameters: RoomPreviewNavigationParameters, completion: (() -> Void)?) {
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared,
parentSpaceId: self.currentSpaceId, previewData: roomPreviewNavigationParameters.previewData)
userIndicatorPresenter: detailUserIndicatorPresenter,
parentSpaceId: self.currentSpaceId,
previewData: roomPreviewNavigationParameters.previewData)
self.showRoom(with: roomCoordinatorParameters,
stackOnSplitViewDetail: roomPreviewNavigationParameters.presentationParameters.stackAboveVisibleViews,
@ -501,6 +521,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
// create room coordinator
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared,
userIndicatorPresenter: detailUserIndicatorPresenter,
session: roomNavigationParameters.mxSession,
parentSpaceId: self.currentSpaceId,
roomId: roomNavigationParameters.roomId,
@ -518,6 +539,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
// create thread coordinator
let threadCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared,
userIndicatorPresenter: detailUserIndicatorPresenter,
session: roomNavigationParameters.mxSession,
parentSpaceId: self.currentSpaceId,
roomId: roomNavigationParameters.roomId,
@ -735,6 +757,7 @@ extension TabBarCoordinator: RoomCoordinatorDelegate {
}
let roomCoordinatorParameters = RoomCoordinatorParameters(navigationRouterStore: NavigationRouterStore.shared,
userIndicatorPresenter: detailUserIndicatorPresenter,
session: matrixSession,
parentSpaceId: self.currentSpaceId,
roomId: roomId,

View file

@ -132,6 +132,7 @@ final class ThreadsCoordinator: NSObject, ThreadsCoordinatorProtocol {
private func createThreadCoordinator(forThreadId threadId: String) -> RoomCoordinator {
let parameters = RoomCoordinatorParameters(navigationRouter: navigationRouter,
navigationRouterStore: nil,
userIndicatorPresenter: parameters.userIndicatorPresenter,
session: parameters.session,
parentSpaceId: nil,
roomId: parameters.roomId,

View file

@ -47,6 +47,7 @@ final class ThreadsCoordinatorBridgePresenter: NSObject {
private let session: MXSession
private let roomId: String
private let threadId: String?
private let userIndicatorPresenter: UserIndicatorTypePresenterProtocol
private var navigationType: NavigationType = .present
private var coordinator: ThreadsCoordinator?
@ -63,10 +64,12 @@ final class ThreadsCoordinatorBridgePresenter: NSObject {
/// - threadId: Thread identifier. Specified thread will be opened if provided, the thread list otherwise
init(session: MXSession,
roomId: String,
threadId: String?) {
threadId: String?,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol) {
self.session = session
self.roomId = roomId
self.threadId = threadId
self.userIndicatorPresenter = userIndicatorPresenter
super.init()
}
@ -81,7 +84,8 @@ final class ThreadsCoordinatorBridgePresenter: NSObject {
let threadsCoordinatorParameters = ThreadsCoordinatorParameters(session: self.session,
roomId: self.roomId,
threadId: self.threadId)
threadId: self.threadId,
userIndicatorPresenter: userIndicatorPresenter)
let threadsCoordinator = ThreadsCoordinator(parameters: threadsCoordinatorParameters)
threadsCoordinator.delegate = self
@ -100,6 +104,7 @@ final class ThreadsCoordinatorBridgePresenter: NSObject {
let threadsCoordinatorParameters = ThreadsCoordinatorParameters(session: self.session,
roomId: self.roomId,
threadId: self.threadId,
userIndicatorPresenter: userIndicatorPresenter,
navigationRouter: navigationRouter)
let threadsCoordinator = ThreadsCoordinator(parameters: threadsCoordinatorParameters)

View file

@ -33,13 +33,17 @@ struct ThreadsCoordinatorParameters {
/// The navigation router that manage physical navigation
let navigationRouter: NavigationRouterType
let userIndicatorPresenter: UserIndicatorTypePresenterProtocol
init(session: MXSession,
roomId: String,
threadId: String?,
userIndicatorPresenter: UserIndicatorTypePresenterProtocol,
navigationRouter: NavigationRouterType? = nil) {
self.session = session
self.roomId = roomId
self.threadId = threadId
self.userIndicatorPresenter = userIndicatorPresenter
self.navigationRouter = navigationRouter ?? NavigationRouter(navigationController: RiotNavigationController())
}
}

View file

@ -0,0 +1,83 @@
//
// 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 XCTest
@testable import CommonKit
@testable import Riot
import MatrixSDK
class UserIndicatorStoreTests: XCTestCase {
class PresenterSpy: UserIndicatorTypePresenterProtocol {
class ViewPresenter: UserIndicatorViewPresentable {
func present() {}
func dismiss() {}
}
var queue = UserIndicatorQueue()
var indicators = [UserIndicator]()
func present(_ type: UserIndicatorType) -> UserIndicator {
let request = UserIndicatorRequest(presenter: ViewPresenter(), dismissal: .manual)
let indicator = queue.add(request)
indicators.append(indicator)
return indicator
}
}
private var presenter: PresenterSpy!
private var store: UserIndicatorStore!
override func setUp() {
presenter = PresenterSpy()
store = UserIndicatorStore(presenter: presenter)
}
func test_presentWillStartIndicator() {
let _ = presentLoading()
XCTAssertEqual(indicator(at: 0)?.state, .executing)
}
func test_cancelWillCompleteIndicator() {
let cancel = presentLoading()
cancel()
XCTAssertEqual(indicator(at: 0)?.state, .completed)
}
func test_cancelWillStartNextIndicator() {
let cancel = presentLoading()
let _ = presentLoading()
cancel()
XCTAssertEqual(indicator(at: 1)?.state, .executing)
}
// MARK: - Helpers
private func presentLoading() -> UserIndicatorCancel {
return store.present(type: .loading(label: "xyz", isInteractionBlocking: false))
}
private func indicator(at index: Int) -> UserIndicator? {
guard index < presenter.indicators.count else {
return nil
}
return presenter.indicators[index]
}
}

1
changelog.d/5604.change Normal file
View file

@ -0,0 +1 @@
Room: New loading indicators when joining room