diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 0996ce2ef..3eb9c33a6 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -210,6 +210,15 @@ final class BuildSettings: NSObject { static let allowInviteExernalUsers: Bool = true + /// Whether a screen uses legacy local activity indicators or improved app-wide indicators + static var appActivityIndicators: Bool { + #if DEBUG + return true + #else + return false + #endif + } + // MARK: - Side Menu static let enableSideMenu: Bool = true static let sideMenuShowInviteFriends: Bool = true diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 95e6dde74..bdefe2dba 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -17,9 +17,11 @@ import Foundation import Intents import MatrixSDK +import CommonKit #if DEBUG import FLEX +import UIKit #endif /// The AppCoordinator is responsible of screen navigation and data injection at root application level. It decides @@ -47,7 +49,7 @@ final class AppCoordinator: NSObject, AppCoordinatorType { return AppNavigator(appCoordinator: self) }() - private weak var splitViewCoordinator: SplitViewCoordinatorType? + fileprivate weak var splitViewCoordinator: SplitViewCoordinatorType? fileprivate weak var sideMenuCoordinator: SideMenuCoordinatorType? private let userSessionsService: UserSessionsService @@ -296,6 +298,18 @@ fileprivate class AppNavigator: AppNavigatorProtocol { return SideMenuPresenter(sideMenuCoordinator: sideMenuCoordinator) }() + private var appNavigationVC: UINavigationController { + guard + let splitVC = appCoordinator.splitViewCoordinator?.toPresentable() as? UISplitViewController, + // Picking out the first view controller currently works only on iPhones, not iPads + let navigationVC = splitVC.viewControllers.first as? UINavigationController + else { + MXLog.error("[AppNavigator] Missing root split view controller") + return UINavigationController() + } + return navigationVC + } + // MARK: - Setup init(appCoordinator: AppCoordinator) { @@ -307,4 +321,16 @@ fileprivate class AppNavigator: AppNavigatorProtocol { func navigate(to destination: AppNavigatorDestination) { self.appCoordinator.navigate(to: destination) } + + func addLoadingActivity() -> Activity { + let presenter = ActivityIndicatorToastPresenter( + text: VectorL10n.roomParticipantsSecurityLoading, + navigationController: appNavigationVC + ) + let request = ActivityRequest( + presenter: presenter, + dismissal: .manual + ) + return ActivityCenter.shared.add(request) + } } diff --git a/Riot/Modules/Application/AppNavigator.swift b/Riot/Modules/Application/AppNavigator.swift index 16c95fb4f..c33b572f4 100644 --- a/Riot/Modules/Application/AppNavigator.swift +++ b/Riot/Modules/Application/AppNavigator.swift @@ -15,6 +15,7 @@ // import Foundation +import CommonKit /// AppNavigatorProtocol abstract a navigator at app level. /// It enables to perform the navigation within the global app scope (open the side menu, open a room and so on) @@ -26,4 +27,12 @@ protocol AppNavigatorProtocol { /// Navigate to a destination screen or a state /// Do not use protocol with associatedtype for the moment like presented here https://www.swiftbysundell.com/articles/navigation-in-swift/#where-to-navigator use a separate enum func navigate(to destination: AppNavigatorDestination) + + /// Add loading activity to an app-wide queue of other activitie + /// + /// If the queue is empty, the activity will be displayed immediately, otherwise it will be pending + /// until the previously added activities have completed / been cancelled. + /// + /// To remove an activity indicator, cancel or deallocate the returned `Activity` + func addLoadingActivity() -> Activity } diff --git a/Riot/Modules/Common/Activities/Activity.swift b/Riot/Modules/Common/Activities/Activity.swift deleted file mode 100644 index b7bc0d083..000000000 --- a/Riot/Modules/Common/Activities/Activity.swift +++ /dev/null @@ -1,91 +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 -import UIKit - -/// An `Activity` represents a temporary visual indicator, such as activity indicator, success notification or an error message. -/// -/// More than one `Activity` may be requested by the system at the same time (e.g. global syncing vs local refresh), -/// and the `ActivityCenter` will ensure that only one activity is shown at a given time, putting the other in a pending queue. -/// -/// A client that requests an activity can specify a default timeout after which the activity is dismissed, or it has to be manually -/// responsible for dismissing it via `cancel` method, or by deallocating itself. -class Activity { - enum State { - case pending - case executing - case completed - } - - private let request: ActivityRequest - private let completion: () -> Void - - private(set) var state: State - - init(request: ActivityRequest, completion: @escaping () -> Void) { - self.request = request - self.completion = completion - - state = .pending - } - - deinit { - cancel() - } - - /// Start the activity - /// - /// Note: clients should not call this method manually if the activity is added into an `ActivityCenter` - func start() { - guard state == .pending else { - return - } - - state = .executing - request.presenter.present() - - switch request.dismissal { - case .manual: - break - case .timeout(let interval): - Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in - self?.complete() - } - } - } - - - /// Cancel the activity, triggering any dismissal action / animation - /// - /// Note: clients can call this method directly, if they have access to the `Activity`. - /// Once cancelled, `ActivityCenter` will automatically start the next `Activity` in the queue. - func cancel() { - complete() - } - - private func complete() { - guard state != .completed else { - return - } - if state == .executing { - request.presenter.dismiss() - } - - state = .completed - completion() - } -} diff --git a/Riot/Modules/Common/Activities/ActivityCenter.swift b/Riot/Modules/Common/Activities/ActivityCenter.swift deleted file mode 100644 index f27d23e53..000000000 --- a/Riot/Modules/Common/Activities/ActivityCenter.swift +++ /dev/null @@ -1,60 +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 - -/// A shared activity center with a single FIFO queue which will ensure only one activity is shown at a given time. -/// -/// `ActivityCenter` offers a `shared` center that can be used by any clients, but clients are also allowed -/// to create local `ActivityCenter` if the context requres multiple simultaneous activities. -class ActivityCenter { - private class Weak { - weak var element: T? - init(_ element: T) { - self.element = element - } - } - - static let shared = ActivityCenter() - private var queue = [Weak]() - - /// Add a new activity to the queue by providing a request. - /// - /// The queue will start the activity right away, if there are no currently running activities, - /// otherwise the activity will be put on hold. - func add(_ request: ActivityRequest) -> Activity { - let activity = Activity(request: request) { [weak self] in - self?.startNextIfIdle() - } - - queue.append(Weak(activity)) - startNextIfIdle() - return activity - } - - private func startNextIfIdle() { - cleanup() - if let activity = queue.first?.element, activity.state == .pending { - activity.start() - } - } - - private func cleanup() { - queue.removeAll { - $0.element == nil || $0.element?.state == .completed - } - } -} diff --git a/Riot/Modules/Common/Activities/ActivityDismissal.swift b/Riot/Modules/Common/Activities/ActivityDismissal.swift deleted file mode 100644 index dbb6cb83a..000000000 --- a/Riot/Modules/Common/Activities/ActivityDismissal.swift +++ /dev/null @@ -1,25 +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 - -/// Different ways in which an `Activity` can be dismissed -enum ActivityDismissal { - /// The `Activity` will not manage the dismissal, but will expect the calling client to do so manually - case manual - /// The `Activity` will be automatically dismissed after `TimeInterval` - case timeout(TimeInterval) -} diff --git a/Riot/Modules/Common/Activities/ActivityPresentable.swift b/Riot/Modules/Common/Activities/ActivityPresentable.swift deleted file mode 100644 index b6e7b46df..000000000 --- a/Riot/Modules/Common/Activities/ActivityPresentable.swift +++ /dev/null @@ -1,25 +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 - -/// A presenter associated with and called by an `Activity`, and responsible for the underlying view shown on the screen. -protocol ActivityPresentable { - /// Called when the `Activity` is started (manually or by the `ActivityCenter`) - func present() - /// Called when the `Activity` is manually cancelled or completed - func dismiss() -} diff --git a/Riot/Modules/Common/Activities/ActivityRequest.swift b/Riot/Modules/Common/Activities/ActivityRequest.swift deleted file mode 100644 index 872699758..000000000 --- a/Riot/Modules/Common/Activities/ActivityRequest.swift +++ /dev/null @@ -1,25 +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 - -/// A request used to create an underlying `Activity`, allowing clients to only specify the visual aspects of an activity. -struct ActivityRequest { - /// Presenter which will manage the underlying view shown on screen - let presenter: ActivityPresentable - // A method in which the activity is eventually dismissed - let dismissal: ActivityDismissal -} diff --git a/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift new file mode 100644 index 000000000..53a5d2453 --- /dev/null +++ b/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift @@ -0,0 +1,47 @@ +// +// 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 +import UIKit +import MatrixSDK +import CommonKit + +/// Presenter which displays activity / loading indicators using app-wide `AppNavigator`, thus displaying them in a unified way, +/// and `ActivityCenter`/`Activity`, which ensures that only one activity is shown at a given time. +/// +/// Note: clients can skip using `AppActivityIndicatorPresenter` and instead coordiinate with `AppNavigatorProtocol` directly. +/// The presenter exists mostly as a transition for view controllers already using `ActivityIndicatorPresenterType` and / or view controllers +/// written in objective-c. +@objc final class AppActivityIndicatorPresenter: NSObject, ActivityIndicatorPresenterType { + private let appNavigator: AppNavigatorProtocol + private var activity: Activity? + + init(appNavigator: AppNavigatorProtocol) { + self.appNavigator = appNavigator + } + + @objc func presentActivityIndicator() { + activity = appNavigator.addLoadingActivity() + } + + @objc func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)?) { + activity = nil + } + + func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)?) { + MXLog.error("[AppActivityIndicatorPresenter] Shared activity indicator does not support presenting from custom views") + } +} diff --git a/Riot/Modules/Common/ActivityIndicator/GlobalActivityCenterPresenter.swift b/Riot/Modules/Common/ActivityIndicator/GlobalActivityCenterPresenter.swift deleted file mode 100644 index b2c35e018..000000000 --- a/Riot/Modules/Common/ActivityIndicator/GlobalActivityCenterPresenter.swift +++ /dev/null @@ -1,69 +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 -import UIKit -import MatrixSDK - -/// Activity indicator presenter which uses a shared `ActivityCenter` to coordinate different activity indicators, -/// and which uses the root navigation view controller to display the activities. -@objc final class GlobalActivityCenterPresenter: NSObject, ActivityIndicatorPresenterType { - private var loadingActivity: Activity? - - private var rootNavigationController: UINavigationController? { - guard - let delegate = UIApplication.shared.delegate as? AppDelegate, - let rootVC = delegate.window?.rootViewController - else { - MXLog.error("[ActivityIndicatorPresenter] Missing root view controller") - return nil - } - - if let vc = (rootVC as? UISplitViewController)?.viewControllers.first as? UINavigationController { - return vc - } else if let vc = rootVC as? UINavigationController { - return vc - } else if let vc = rootVC.navigationController { - return vc - } - return nil - } - - @objc func presentActivityIndicator(animated: Bool) { - guard let vc = rootNavigationController else { - MXLog.error("[ActivityIndicatorPresenter] Missing available navigation controller") - return - } - - let presenter = ActivityIndicatorToastPresenter( - text: VectorL10n.roomParticipantsSecurityLoading, - navigationController: vc - ) - let request = ActivityRequest( - presenter: presenter, - dismissal: .manual - ) - loadingActivity = ActivityCenter.shared.add(request) - } - - func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)?) { - MXLog.error("[ActivityIndicatorPresenter] Shared activity indicator needs to be presented on a view controller") - } - - @objc func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)?) { - loadingActivity = nil - } -} diff --git a/Riot/Modules/Common/Recents/RecentsViewController.h b/Riot/Modules/Common/Recents/RecentsViewController.h index 9e9e357a7..00f728d24 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.h +++ b/Riot/Modules/Common/Recents/RecentsViewController.h @@ -19,6 +19,7 @@ @class RootTabEmptyView; @class AnalyticsScreenTimer; +@class AppActivityIndicatorPresenter; /** Notification to be posted when recents data is ready. Notification object will be the RecentsViewController instance. @@ -96,6 +97,11 @@ FOUNDATION_EXPORT NSString *const RecentsViewControllerDataReadyNotification; */ @property (nonatomic) AnalyticsScreenTimer *screenTimer; +/** + Presenter for displaying app-wide activity / loading indicators. If not set, the view controller will use legacy activity indicators + */ +@property (nonatomic, strong) AppActivityIndicatorPresenter *activityPresenter; + /** Return the sticky header for the specified section of the table view diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index 61efd306f..dbd280337 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -80,8 +80,6 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro @property (nonatomic, strong) RoomNotificationSettingsCoordinatorBridgePresenter *roomNotificationSettingsCoordinatorBridgePresenter; -@property (nonatomic, strong) GlobalActivityCenterPresenter *activityPresenter; - @end @implementation RecentsViewController @@ -141,8 +139,6 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. - self.activityPresenter = [[GlobalActivityCenterPresenter alloc] init]; - self.recentsTableView.accessibilityIdentifier = @"RecentsVCTableView"; // Register here the customized cell view class used to render recents @@ -2414,15 +2410,23 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro #pragma mark - Activity Indicator - (BOOL)providesCustomActivityIndicator { - return YES; + return self.activityPresenter != nil; } - (void)startActivityIndicator { - [self.activityPresenter presentActivityIndicatorWithAnimated:YES]; + if (self.activityPresenter) { + [self.activityPresenter presentActivityIndicator]; + } else { + [super startActivityIndicator]; + } } - (void)stopActivityIndicator { - [self.activityPresenter removeCurrentActivityIndicatorWithAnimated:YES completion:nil]; + if (self.activityPresenter) { + [self.activityPresenter removeCurrentActivityIndicatorWithAnimated:YES completion:nil]; + } else { + [super stopActivityIndicator]; + } } @end diff --git a/Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift b/Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift index bccef0df7..1c592a2a2 100644 --- a/Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift +++ b/Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift @@ -16,32 +16,38 @@ import Foundation import UIKit +import CommonKit +/// An `ActivityPresenter` responsible for showing / hiding a toast view for activity indicators, and managed by an `Activity`, +/// meaning the `present` and `dismiss` methods will be called when the parent `Activity` starts or completes. class ActivityIndicatorToastPresenter: ActivityPresentable { + private let text: String + private weak var navigationController: UINavigationController? private weak var view: UIView? init(text: String, navigationController: UINavigationController) { + self.text = text + self.navigationController = navigationController + } + + func present() { + guard let navigationController = navigationController else { + return + } + let view = ActivityIndicatorToastView(text: text) + view.update(theme: ThemeService.shared().theme) + self.view = view view.translatesAutoresizingMaskIntoConstraints = false navigationController.view.addSubview(view) NSLayoutConstraint.activate([ view.centerXAnchor.constraint(equalTo: navigationController.navigationBar.centerXAnchor), - view.centerYAnchor.constraint(equalTo: navigationController.navigationBar.bottomAnchor) + view.topAnchor.constraint(equalTo: navigationController.navigationBar.bottomAnchor) ]) - view.isHidden = true - self.view = view - } - - func present() { - guard let view = view else { - return - } - view.alpha = 0 - view.isHidden = false - view.transform = .init(translationX: 0, y: 10) + view.transform = .init(translationX: 0, y: 5) UIView.animate(withDuration: 0.2) { view.alpha = 1 view.transform = .identity @@ -60,7 +66,7 @@ class ActivityIndicatorToastPresenter: ActivityPresentable { DispatchQueue.main.async { UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState) { view.alpha = 0 - view.transform = .init(translationX: 0, y: -10) + view.transform = .init(translationX: 0, y: -5) } completion: { _ in view.removeFromSuperview() self.view = nil diff --git a/Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift b/Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift index a4bd67259..6964ec445 100644 --- a/Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift +++ b/Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift @@ -20,34 +20,27 @@ import DesignKit class ActivityIndicatorToastView: UIView, Themable { private struct Constants { - static let padding: UIEdgeInsets = UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12) + static let padding = UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12) + static let shadowOffset = CGSize(width: 0, height: 4) + static let shadowRadius = CGFloat(12) + static let shadowOpacity = Float(0.1) } - private lazy var stackView: UIStackView = { + private let stackView: UIStackView = { let stack = UIStackView() stack.axis = .horizontal stack.spacing = 5 - - addSubview(stack) - stack.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - stack.topAnchor.constraint(equalTo: topAnchor, constant: Constants.padding.top), - stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.padding.bottom), - stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.padding.left), - stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.padding.right) - ]) - return stack }() - private lazy var activityIndicator: UIActivityIndicatorView = { + private let activityIndicator: UIActivityIndicatorView = { let view = UIActivityIndicatorView() view.transform = .init(scaleX: 0.75, y: 0.75) view.startAnimating() return view }() - private lazy var label: UILabel = { + private let label: UILabel = { return UILabel() }() @@ -62,18 +55,33 @@ class ActivityIndicatorToastView: UIView, Themable { private func setup(text: String) { setupLayer() + setupStackView() stackView.addArrangedSubview(activityIndicator) stackView.addArrangedSubview(label) label.text = text - update(theme: ThemeService.shared().theme) + } + + private func setupStackView() { + addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.padding.top), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.padding.bottom), + stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.padding.left), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.padding.right) + ]) } private func setupLayer() { - layer.cornerRadius = 20 layer.shadowColor = UIColor.black.cgColor - layer.shadowOffset = .init(width: 0, height: 4) - layer.shadowRadius = 12 - layer.shadowOpacity = 0.1 + layer.shadowOffset = Constants.shadowOffset + layer.shadowRadius = Constants.shadowRadius + layer.shadowOpacity = Constants.shadowOpacity + } + + override func layoutSubviews() { + super.layoutSubviews() + layer.cornerRadius = layer.frame.height / 2 } func update(theme: Theme) { diff --git a/Riot/Modules/MatrixKit/Controllers/MXKViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKViewController.h index 66237c8d6..e75d42e11 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKViewController.h +++ b/Riot/Modules/MatrixKit/Controllers/MXKViewController.h @@ -48,10 +48,5 @@ */ @property CGFloat keyboardHeight; -/** - Returns `YES` if any `MXSession` currently requires the display of an activity indicator. - */ -@property (nonatomic, readonly) BOOL shouldShowActivityIndicator; - @end diff --git a/Riot/Modules/MatrixKit/Controllers/MXKViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKViewController.m index 118e72455..a4e559b4c 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKViewController.m +++ b/Riot/Modules/MatrixKit/Controllers/MXKViewController.m @@ -492,21 +492,18 @@ const CGFloat MXKViewControllerMaxExternalKeyboardHeight = 80; #pragma mark - Activity indicator -- (BOOL)shouldShowActivityIndicator { +- (void)stopActivityIndicator +{ + // Check whether all conditions are satisfied before stopping loading wheel + BOOL isActivityInProgress = NO; for (MXSession *mxSession in mxSessionArray) { if (mxSession.shouldShowActivityIndicator) { - return YES; + isActivityInProgress = YES; } } - return NO; -} - -- (void)stopActivityIndicator -{ - // Check whether all conditions are satisfied before stopping loading wheel - if (!self.shouldShowActivityIndicator) + if (!isActivityInProgress) { [super stopActivityIndicator]; } diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index 2473e9d46..a4a0f7dcf 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -154,8 +154,6 @@ */ @property (nonatomic, weak) MXKAttachmentsViewController *attachmentsViewer; -@property (nonatomic, strong) GlobalActivityCenterPresenter *activityPresenter; - @end @implementation MXKRoomViewController @@ -223,8 +221,6 @@ { [super viewDidLoad]; - _activityPresenter = [[GlobalActivityCenterPresenter alloc] init]; - // Check whether the view controller has been pushed via storyboard if (!_bubblesTableView) { @@ -1776,15 +1772,6 @@ #pragma mark - activity indicator -- (BOOL)providesCustomActivityIndicator { - return YES; -} - -- (void)startActivityIndicator -{ - [self.activityPresenter presentActivityIndicatorWithAnimated:YES]; -} - - (void)stopActivityIndicator { // Keep the loading wheel displayed while we are joining the room @@ -1800,9 +1787,8 @@ return; } - if (![self shouldShowActivityIndicator]) { - [self.activityPresenter removeCurrentActivityIndicatorWithAnimated:YES completion:nil]; - } + // Leave super decide + [super stopActivityIndicator]; } #pragma mark - Pagination diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index c0454e86c..30ce90bc0 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -227,6 +227,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { homeViewController.tabBarItem.image = homeViewController.tabBarItem.image homeViewController.accessibilityLabel = VectorL10n.titleHome + if BuildSettings.appActivityIndicators { + homeViewController.activityPresenter = AppActivityIndicatorPresenter(appNavigator: parameters.appNavigator) + } + let wrapperViewController = HomeViewControllerWithBannerWrapperViewController(viewController: homeViewController) return wrapperViewController } diff --git a/changelog.d/4829.change b/changelog.d/4829.change index e4f187451..e55f8b1fd 100644 --- a/changelog.d/4829.change +++ b/changelog.d/4829.change @@ -1 +1 @@ -CommonKit: Create a new framework with common functionality and create Activity and ActivityCenter +ActivityCenter: Use ActivityCenter to show loading indicators on the home screen (in DEBUG builds only)