Update activity indicators on leaving room

Signed-off-by: Andy Uhnak <andyuhnak@gmail.com>
This commit is contained in:
Andy Uhnak
2022-02-17 11:10:55 +00:00
parent 2e88df4576
commit a5ae727f84
18 changed files with 437 additions and 84 deletions

View File

@@ -26,7 +26,7 @@ import UIKit
/// 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.
public class Activity {
enum State {
public enum State {
case pending
case executing
case completed
@@ -35,7 +35,7 @@ public class Activity {
private let request: ActivityRequest
private let completion: () -> Void
private(set) var state: State
public private(set) var state: State
public init(request: ActivityRequest, completion: @escaping () -> Void) {
self.request = request
@@ -45,7 +45,7 @@ public class Activity {
}
deinit {
cancel()
complete()
}
internal func start() {
@@ -70,7 +70,7 @@ public class Activity {
///
/// 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() {
public func cancel() {
complete()
}
@@ -92,3 +92,11 @@ public extension Activity {
collection.append(self)
}
}
public extension Collection where Element == Activity {
func cancelAll() {
forEach {
$0.cancel()
}
}
}

View File

@@ -290,6 +290,8 @@ Tap the + to start adding people.";
"room_participants_leave_prompt_title_for_dm" = "Leave";
"room_participants_leave_prompt_msg" = "Are you sure you want to leave the room?";
"room_participants_leave_prompt_msg_for_dm" = "Are you sure you want to leave?";
"room_participants_leave_processing" = "Leaving";
"room_participants_leave_success" = "Left room";
"room_participants_remove_prompt_title" = "Confirmation";
"room_participants_remove_prompt_msg" = "Are you sure you want to remove %@ from this chat?";
"room_participants_remove_third_party_invite_prompt_msg" = "Are you sure you want to revoke this invite?";
@@ -1747,6 +1749,7 @@ Tap the + to start adding people.";
"home_context_menu_low_priority" = "Low priority";
"home_context_menu_normal_priority" = "Normal priority";
"home_context_menu_leave" = "Leave";
"home_syncing" = "Syncing";
// MARK: - Favourites

View File

@@ -60,7 +60,7 @@ extension UIView {
duration: TimeInterval = Constants.defaultDuration,
position: ToastPosition = Constants.defaultPosition,
additionalMargin: CGFloat = 0.0) {
let view = BasicToastView(withMessage: message, image: image)
let view = RectangleToastView(withMessage: message, image: image)
vc_toast(view: view, duration: duration, position: position, additionalMargin: additionalMargin)
}

View File

@@ -1647,6 +1647,10 @@ public class VectorL10n: NSObject {
public static func homeEmptyViewTitle(_ p1: String, _ p2: String) -> String {
return VectorL10n.tr("Vector", "home_empty_view_title", p1, p2)
}
/// Syncing
public static var homeSyncing: String {
return VectorL10n.tr("Vector", "home_syncing")
}
/// Could not connect to the homeserver.
public static var homeserverConnectionLost: String {
return VectorL10n.tr("Vector", "homeserver_connection_lost")
@@ -3687,6 +3691,10 @@ public class VectorL10n: NSObject {
public static var roomParticipantsInvitedSection: String {
return VectorL10n.tr("Vector", "room_participants_invited_section")
}
/// Leaving
public static var roomParticipantsLeaveProcessing: String {
return VectorL10n.tr("Vector", "room_participants_leave_processing")
}
/// Are you sure you want to leave the room?
public static var roomParticipantsLeavePromptMsg: String {
return VectorL10n.tr("Vector", "room_participants_leave_prompt_msg")
@@ -3703,6 +3711,10 @@ public class VectorL10n: NSObject {
public static var roomParticipantsLeavePromptTitleForDm: String {
return VectorL10n.tr("Vector", "room_participants_leave_prompt_title_for_dm")
}
/// Left room
public static var roomParticipantsLeaveSuccess: String {
return VectorL10n.tr("Vector", "room_participants_leave_success")
}
/// %d participants
public static func roomParticipantsMultiParticipants(_ p1: Int) -> String {
return VectorL10n.tr("Vector", "room_participants_multi_participants", p1)

View File

@@ -322,15 +322,39 @@ fileprivate class AppNavigator: AppNavigatorProtocol {
self.appCoordinator.navigate(to: destination)
}
func addLoadingActivity() -> Activity {
let presenter = ActivityIndicatorToastPresenter(
text: VectorL10n.roomParticipantsSecurityLoading,
navigationController: appNavigationVC
)
let request = ActivityRequest(
presenter: presenter,
dismissal: .manual
)
func addAppActivity(_ type: AppActivityType) -> Activity {
let request = activityRequest(for: type)
return ActivityCenter.shared.add(request)
}
// MARK: - Private
private func activityRequest(for type: AppActivityType) -> ActivityRequest {
switch type {
case let .loading(label):
let presenter = ToastActivityPresenter(
viewState: .init(
style: .loading,
label: label
),
navigationController: appNavigationVC
)
return ActivityRequest(
presenter: presenter,
dismissal: .manual
)
case let .success(label):
let presenter = ToastActivityPresenter(
viewState: .init(
style: .success,
label: label
),
navigationController: appNavigationVC
)
return ActivityRequest(
presenter: presenter,
dismissal: .timeout(1.5)
)
}
}
}

View File

@@ -17,6 +17,15 @@
import Foundation
import CommonKit
/// Type of activity to be shown in the app navigator
enum AppActivityType {
/// Loading toast with custom label
case loading(String)
/// Success toast with custom label
case success(String)
}
/// 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)
/// Note: Presentation of the pattern here https://www.swiftbysundell.com/articles/navigation-in-swift/#where-to-navigator
@@ -28,11 +37,11 @@ protocol AppNavigatorProtocol {
/// 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
/// Add new activity, such as loading indicator or a success message, to an app-wide queue of other activities
///
/// 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
func addAppActivity(_ type: AppActivityType) -> Activity
}

View File

@@ -27,21 +27,36 @@ import CommonKit
/// written in objective-c.
@objc final class AppActivityIndicatorPresenter: NSObject, ActivityIndicatorPresenterType {
private let appNavigator: AppNavigatorProtocol
private var activity: Activity?
private var loadingActivity: Activity?
private var otherActivities = [Activity]()
init(appNavigator: AppNavigatorProtocol) {
self.appNavigator = appNavigator
}
@objc func presentActivityIndicator() {
activity = appNavigator.addLoadingActivity()
presentActivityIndicator(label: VectorL10n.homeSyncing)
}
@objc func presentActivityIndicator(label: String) {
guard loadingActivity == nil || loadingActivity?.state == .completed else {
// The app is very liberal with calling `presentActivityIndicator` (often not matched by corresponding `removeCurrentActivityIndicator`),
// so there is no reason to keep adding new activity indiciators if there is one already showing.
return
}
loadingActivity = appNavigator.addAppActivity(.loading(label))
}
@objc func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)?) {
activity = nil
loadingActivity = nil
}
func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)?) {
MXLog.error("[AppActivityIndicatorPresenter] Shared activity indicator does not support presenting from custom views")
}
@objc func presentSuccess(label: String) {
appNavigator.addAppActivity(.success(label)).store(in: &otherActivities)
}
}

View File

@@ -0,0 +1,53 @@
//
// 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 CommonKit
import UIKit
/// Presenter which displays fullscreen activity / loading indicators, and conforming to legacy `ActivityIndicatorPresenterType`,
/// but interally wrapping an `ActivityPresenter` which is used in conjuction to `Activity` and `ActivityCenter`.
///
/// Note: clients can skip using `FullscreenActivityIndicatorPresenter` 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 FullscreenActivityIndicatorPresenter: NSObject, ActivityIndicatorPresenterType {
private let label: String
private weak var viewController: UIViewController?
private var activity: Activity?
init(label: String, on viewController: UIViewController) {
self.label = label
self.viewController = viewController
}
func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)?) {
guard let vc = viewController else {
return
}
let request = ActivityRequest(
presenter: FullscreenLoadingActivityPresenter(label: label, on: vc),
dismissal: .manual
)
activity = ActivityCenter.shared.add(request)
}
@objc func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)?) {
activity?.cancel()
}
}

View File

@@ -0,0 +1,100 @@
//
// 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
final class LabelledActivityIndicatorView: UIView, Themable {
private enum Constants {
static let padding = UIEdgeInsets(top: 20, left: 40, bottom: 15, right: 40)
static let activityIndicatorScale = CGFloat(1.5)
static let cornerRadius: CGFloat = 12.0
static let stackBackgroundOpacity: CGFloat = 0.9
static let stackSpacing: CGFloat = 15
static let backgroundOpacity: CGFloat = 0.5
}
private let stackBackgroundView: UIView = {
let view = UIView()
view.layer.cornerRadius = Constants.cornerRadius
view.alpha = Constants.stackBackgroundOpacity
return view
}()
private let stackView: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fill
stack.alignment = .center
stack.spacing = Constants.stackSpacing
return stack
}()
private let activityIndicator: UIActivityIndicatorView = {
let view = UIActivityIndicatorView()
view.transform = .init(scaleX: Constants.activityIndicatorScale, y: Constants.activityIndicatorScale)
view.startAnimating()
return view
}()
private let label: UILabel = {
return UILabel()
}()
init(text: String) {
super.init(frame: .zero)
setup(text: text)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(text: String) {
setupStackView()
label.text = text
}
private func setupStackView() {
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
stackView.addArrangedSubview(activityIndicator)
stackView.addArrangedSubview(label)
insertSubview(stackBackgroundView, belowSubview: stackView)
stackBackgroundView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackBackgroundView.topAnchor.constraint(equalTo: stackView.topAnchor, constant: -Constants.padding.top),
stackBackgroundView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: Constants.padding.bottom),
stackBackgroundView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: -Constants.padding.left),
stackBackgroundView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: Constants.padding.right)
])
}
func update(theme: Theme) {
backgroundColor = theme.colors.primaryContent.withAlphaComponent(Constants.backgroundOpacity)
stackBackgroundView.backgroundColor = theme.colors.system
activityIndicator.color = theme.colors.secondaryContent
label.font = theme.fonts.calloutSB
label.textColor = theme.colors.secondaryContent
}
}

View File

@@ -0,0 +1,74 @@
//
// 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 CommonKit
import UIKit
/// An `ActivityPresenter` responsible for showing / hiding a full-screen loading view that obscures (and thus disables) all other controls.
/// It is managed by an `Activity`, meaning the `present` and `dismiss` methods will be called when the parent `Activity` starts or completes.
class FullscreenLoadingActivityPresenter: ActivityPresentable {
private let label: String
private weak var viewController: UIViewController?
private weak var view: UIView?
init(label: String, on viewController: UIViewController) {
self.label = label
self.viewController = viewController
}
func present() {
// Find the current top navigation controller
var vc: UIViewController? = viewController
while vc?.navigationController != nil {
vc = vc?.navigationController
}
guard let presentingVC = vc else {
return
}
let view = LabelledActivityIndicatorView(text: label)
view.update(theme: ThemeService.shared().theme)
self.view = view
view.translatesAutoresizingMaskIntoConstraints = false
presentingVC.view.addSubview(view)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: presentingVC.view.topAnchor),
view.bottomAnchor.constraint(equalTo: presentingVC.view.bottomAnchor),
view.leadingAnchor.constraint(equalTo: presentingVC.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: presentingVC.view.trailingAnchor)
])
view.alpha = 0
CATransaction.commit()
UIView.animate(withDuration: 0.2) {
view.alpha = 1
}
}
func dismiss() {
guard let view = view, view.superview != nil else {
return
}
UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState) {
view.alpha = 0
} completion: { _ in
view.removeFromSuperview()
}
}
}

View File

@@ -17,16 +17,17 @@
import Foundation
import UIKit
import CommonKit
import MatrixSDK
/// 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
/// An `ActivityPresenter` responsible for showing / hiding a toast view for activity indicators or success messages.
/// It is managed by an `Activity`, meaning the `present` and `dismiss` methods will be called when the parent `Activity` starts or completes.
class ToastActivityPresenter: ActivityPresentable {
private let viewState: RoundedToastView.ViewState
private weak var navigationController: UINavigationController?
private weak var view: UIView?
init(text: String, navigationController: UINavigationController) {
self.text = text
init(viewState: RoundedToastView.ViewState, navigationController: UINavigationController) {
self.viewState = viewState
self.navigationController = navigationController
}
@@ -35,7 +36,7 @@ class ActivityIndicatorToastPresenter: ActivityPresentable {
return
}
let view = ActivityIndicatorToastView(text: text)
let view = RoundedToastView(viewState: viewState)
view.update(theme: ThemeService.shared().theme)
self.view = view
@@ -47,7 +48,9 @@ class ActivityIndicatorToastPresenter: ActivityPresentable {
])
view.alpha = 0
CATransaction.flush()
view.transform = .init(translationX: 0, y: 5)
UIView.animate(withDuration: 0.2) {
view.alpha = 1
view.transform = .identity
@@ -59,18 +62,11 @@ class ActivityIndicatorToastPresenter: ActivityPresentable {
return
}
// If `present` and `dismiss` are called right after each other without delay,
// the view does not correctly pick up `currentState` of alpha. Dispatching onto
// the main queue skips a few run loops, giving the system time to render
// current state.
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState) {
view.alpha = 0
view.transform = .init(translationX: 0, y: -5)
} completion: { _ in
view.removeFromSuperview()
self.view = nil
}
UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState) {
view.alpha = 0
view.transform = .init(translationX: 0, y: -5)
} completion: { _ in
view.removeFromSuperview()
}
}
}

View File

@@ -1280,8 +1280,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
MXRoom *room = [self.mainSession roomWithRoomId:currentRoomId];
if (room)
{
[self startActivityIndicator];
[self startActivityIndicatorWithLabel:[VectorL10n roomParticipantsLeaveProcessing]];
// cancel pending uploads/downloads
// they are useless by now
[MXMediaManager cancelDownloadsInCacheFolder:room.roomId];
@@ -1296,6 +1295,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
{
typeof(self) self = weakSelf;
[self stopActivityIndicator];
[self.activityPresenter presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]];
// Force table refresh
[self cancelEditionMode:YES];
}
@@ -2413,6 +2413,14 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro
return self.activityPresenter != nil;
}
- (void)startActivityIndicatorWithLabel:(NSString *)label {
if (self.activityPresenter) {
[self.activityPresenter presentActivityIndicatorWithLabel:label];
} else {
[super startActivityIndicator];
}
}
- (void)startActivityIndicator {
if (self.activityPresenter) {
[self.activityPresenter presentActivityIndicator];

View File

@@ -17,7 +17,7 @@
import Foundation
import UIKit
class BasicToastView: UIView, Themable {
class RectangleToastView: UIView, Themable {
private enum Constants {
static let padding: UIEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)

View File

@@ -18,14 +18,34 @@ import Foundation
import UIKit
import DesignKit
class ActivityIndicatorToastView: UIView, Themable {
class RoundedToastView: UIView, Themable {
private struct Constants {
static let padding = UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12)
static let activityIndicatorScale = CGFloat(0.75)
static let imageViewSize = CGFloat(15)
static let shadowOffset = CGSize(width: 0, height: 4)
static let shadowRadius = CGFloat(12)
static let shadowOpacity = Float(0.1)
}
private lazy var activityIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView()
indicator.transform = .init(scaleX: Constants.activityIndicatorScale, y: Constants.activityIndicatorScale)
indicator.startAnimating()
return indicator
}()
private lazy var imagView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: Constants.imageViewSize),
imageView.heightAnchor.constraint(equalToConstant: Constants.imageViewSize),
])
return imageView
}()
private let stackView: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
@@ -33,32 +53,25 @@ class ActivityIndicatorToastView: UIView, Themable {
return stack
}()
private let activityIndicator: UIActivityIndicatorView = {
let view = UIActivityIndicatorView()
view.transform = .init(scaleX: 0.75, y: 0.75)
view.startAnimating()
return view
}()
private let label: UILabel = {
return UILabel()
}()
init(text: String) {
init(viewState: ViewState) {
super.init(frame: .zero)
setup(text: text)
setup(viewState: viewState)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(text: String) {
private func setup(viewState: ViewState) {
setupLayer()
setupStackView()
stackView.addArrangedSubview(activityIndicator)
stackView.addArrangedSubview(toastView(for: viewState.style))
stackView.addArrangedSubview(label)
label.text = text
label.text = viewState.label
}
private func setupStackView() {
@@ -86,6 +99,29 @@ class ActivityIndicatorToastView: UIView, Themable {
func update(theme: Theme) {
backgroundColor = UIColor.white
stackView.arrangedSubviews.first?.tintColor = theme.colors.primaryContent
label.font = theme.fonts.subheadline
label.textColor = theme.colors.primaryContent
}
private func toastView(for style: Style) -> UIView {
switch style {
case .loading:
return activityIndicator
case .success:
imagView.image = Asset.Images.checkmark.image
return imagView
}
}
}
extension RoundedToastView {
enum Style {
case loading
case success
}
struct ViewState {
let style: Style
let label: String
}
}

View File

@@ -17,6 +17,8 @@
*/
import UIKit
import CommonKit
import MatrixSDK
final class RoomInfoListViewController: UIViewController {
@@ -38,7 +40,7 @@ final class RoomInfoListViewController: UIViewController {
private var viewModel: RoomInfoListViewModelType!
private var theme: Theme!
private var errorPresenter: MXKErrorPresentation!
private var activityPresenter: ActivityIndicatorPresenter!
private var activityPresenter: ActivityIndicatorPresenterType!
private var isRoomDirect: Bool = false
private var screenTimer = AnalyticsScreenTimer(screen: .roomDetails)
@@ -114,7 +116,14 @@ final class RoomInfoListViewController: UIViewController {
// Do any additional setup after loading the view.
self.setupViews()
self.activityPresenter = ActivityIndicatorPresenter()
if BuildSettings.appActivityIndicators {
self.activityPresenter = FullscreenActivityIndicatorPresenter(
label: VectorL10n.roomParticipantsLeaveProcessing,
on: self
)
} else {
self.activityPresenter = ActivityIndicatorPresenter()
}
self.errorPresenter = MXKErrorAlertPresentation()
self.registerThemeServiceDidChangeThemeNotification()
@@ -143,6 +152,7 @@ final class RoomInfoListViewController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
screenTimer.stop()
activityPresenter.removeCurrentActivityIndicator(animated: animated)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {

View File

@@ -1066,15 +1066,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
self.jumpToLastUnreadBannerContainer.hidden = YES;
[super leaveRoomOnEvent:event];
if (self.delegate)
{
[self.delegate roomViewControllerDidLeaveRoom:self];
}
else
{
[[AppDelegate theDelegate] restoreInitialDisplay:nil];
}
[self notifyDelegateOnLeaveRoomIfNecessary];
}
// Set the input toolbar according to the current display
@@ -2192,16 +2184,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
[self.roomDataSource.room leave:^{
[self stopActivityIndicator];
// We remove the current view controller.
if (self.delegate)
{
[self.delegate roomViewControllerDidLeaveRoom:self];
}
else
{
[[AppDelegate theDelegate] restoreInitialDisplay:^{}];
}
[self notifyDelegateOnLeaveRoomIfNecessary];
} failure:^(NSError *error) {
@@ -2211,6 +2194,22 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
}];
}
- (void)notifyDelegateOnLeaveRoomIfNecessary {
if (self.delegate)
{
// Leaving room often triggers multiple events, incl local delegate callbacks as well as global notifications,
// which may lead to multiple identical UI changes (navigating to home, displaying notification etc).
// To avoid this, as soon as we notify the delegate the first time, we nilify it, preventing future messages
// from being passed along, assuming that after leaving a room there is nothing else to communicate to the delegate.
[self.delegate roomViewControllerDidLeaveRoom:self];
self.delegate = nil;
}
else
{
[[AppDelegate theDelegate] restoreInitialDisplay:^{}];
}
}
- (void)roomPreviewDidTapCancelAction
{
// Decline this invitation = leave this page
@@ -7052,14 +7051,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05;
- (void)roomInfoCoordinatorBridgePresenterDelegateDidLeaveRoom:(RoomInfoCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
if (self.delegate)
{
[self.delegate roomViewControllerDidLeaveRoom:self];
}
else
{
[[AppDelegate theDelegate] restoreInitialDisplay:nil];
}
[self notifyDelegateOnLeaveRoomIfNecessary];
}
#pragma mark - RemoveJitsiWidgetViewDelegate

View File

@@ -17,6 +17,7 @@
*/
import UIKit
import CommonKit
@objcMembers
final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
@@ -53,6 +54,8 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
return self.navigationRouter.modules.last is MasterTabBarController
}
private var activities = [Activity]()
// MARK: Public
// Must be used only internally
@@ -73,6 +76,10 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType {
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
}
deinit {
activities.cancelAll()
}
// MARK: - Public methods
func start() {
@@ -702,6 +709,11 @@ extension TabBarCoordinator: RoomCoordinatorDelegate {
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
// For the moment when a room is left, reset the split detail with placeholder
self.resetSplitViewDetails()
if BuildSettings.appActivityIndicators {
parameters.appNavigator
.addAppActivity(.success(VectorL10n.roomParticipantsLeaveSuccess))
.store(in: &activities)
}
}
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {

1
changelog.d/5605.change Normal file
View File

@@ -0,0 +1 @@
Activity Indicators: Update loading and success messages when leaving room