From 9eae7c02bb08a6678f38e6a52542f0cd3ebb2242 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 17 Feb 2022 11:10:55 +0000 Subject: [PATCH 001/139] Update activity indicators on leaving room Signed-off-by: Andy Uhnak --- CommonKit/Source/Activity/Activity.swift | 16 ++- Riot/Assets/en.lproj/Vector.strings | 3 + Riot/Categories/UIView+Toast.swift | 2 +- Riot/Generated/Strings.swift | 12 +++ Riot/Modules/Application/AppCoordinator.swift | 42 ++++++-- Riot/Modules/Application/AppNavigator.swift | 13 ++- .../AppActivityIndicatorPresenter.swift | 23 +++- ...FullscreenActivityIndicatorPresenter.swift | 53 ++++++++++ .../UIKit/LabelledActivityIndicatorView.swift | 100 ++++++++++++++++++ .../FullscreenLoadingActivityPresenter.swift | 74 +++++++++++++ .../ToastActivityPresenter.swift} | 34 +++--- .../Common/Recents/RecentsViewController.m | 12 ++- ...astView.swift => RectangleToastView.swift} | 2 +- ...ToastView.swift => RoundedToastView.swift} | 62 ++++++++--- .../RoomInfoListViewController.swift | 14 ++- Riot/Modules/Room/RoomViewController.m | 46 ++++---- Riot/Modules/TabBar/TabBarCoordinator.swift | 12 +++ changelog.d/5605.change | 1 + 18 files changed, 437 insertions(+), 84 deletions(-) create mode 100644 Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift create mode 100644 Riot/Modules/Common/ActivityIndicator/UIKit/LabelledActivityIndicatorView.swift create mode 100644 Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift rename Riot/Modules/Common/{Toasts/ActivityIndicatorToastPresenter.swift => ActivityPresenters/ToastActivityPresenter.swift} (63%) rename Riot/Modules/Common/Toasts/{BasicToastView.swift => RectangleToastView.swift} (98%) rename Riot/Modules/Common/Toasts/{ActivityIndicatorToastView.swift => RoundedToastView.swift} (59%) create mode 100644 changelog.d/5605.change diff --git a/CommonKit/Source/Activity/Activity.swift b/CommonKit/Source/Activity/Activity.swift index d72e87800..5c8eaac0b 100644 --- a/CommonKit/Source/Activity/Activity.swift +++ b/CommonKit/Source/Activity/Activity.swift @@ -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() + } + } +} diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index ca75a5a65..bf3887a30 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -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 diff --git a/Riot/Categories/UIView+Toast.swift b/Riot/Categories/UIView+Toast.swift index b89d63ac1..1d3205704 100644 --- a/Riot/Categories/UIView+Toast.swift +++ b/Riot/Categories/UIView+Toast.swift @@ -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) } diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 1313a6028..37fa5173d 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -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) diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index 8f1b61d58..a30a69e68 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -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) + ) + } + } } diff --git a/Riot/Modules/Application/AppNavigator.swift b/Riot/Modules/Application/AppNavigator.swift index c33b572f4..030c25c04 100644 --- a/Riot/Modules/Application/AppNavigator.swift +++ b/Riot/Modules/Application/AppNavigator.swift @@ -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 } diff --git a/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift index 53a5d2453..d3bd3de28 100644 --- a/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift @@ -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) + } } diff --git a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift new file mode 100644 index 000000000..7656e15fb --- /dev/null +++ b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift @@ -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() + } +} diff --git a/Riot/Modules/Common/ActivityIndicator/UIKit/LabelledActivityIndicatorView.swift b/Riot/Modules/Common/ActivityIndicator/UIKit/LabelledActivityIndicatorView.swift new file mode 100644 index 000000000..266237bd4 --- /dev/null +++ b/Riot/Modules/Common/ActivityIndicator/UIKit/LabelledActivityIndicatorView.swift @@ -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 + } +} diff --git a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift new file mode 100644 index 000000000..0ca4cb473 --- /dev/null +++ b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift @@ -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() + } + } +} diff --git a/Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift b/Riot/Modules/Common/ActivityPresenters/ToastActivityPresenter.swift similarity index 63% rename from Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift rename to Riot/Modules/Common/ActivityPresenters/ToastActivityPresenter.swift index 1c592a2a2..f0bff4c17 100644 --- a/Riot/Modules/Common/Toasts/ActivityIndicatorToastPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/ToastActivityPresenter.swift @@ -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() } } } diff --git a/Riot/Modules/Common/Recents/RecentsViewController.m b/Riot/Modules/Common/Recents/RecentsViewController.m index dbd280337..ad09e064d 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -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]; diff --git a/Riot/Modules/Common/Toasts/BasicToastView.swift b/Riot/Modules/Common/Toasts/RectangleToastView.swift similarity index 98% rename from Riot/Modules/Common/Toasts/BasicToastView.swift rename to Riot/Modules/Common/Toasts/RectangleToastView.swift index a4b9a39b6..41aa3c65f 100644 --- a/Riot/Modules/Common/Toasts/BasicToastView.swift +++ b/Riot/Modules/Common/Toasts/RectangleToastView.swift @@ -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) diff --git a/Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift b/Riot/Modules/Common/Toasts/RoundedToastView.swift similarity index 59% rename from Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift rename to Riot/Modules/Common/Toasts/RoundedToastView.swift index 6964ec445..303bcbcec 100644 --- a/Riot/Modules/Common/Toasts/ActivityIndicatorToastView.swift +++ b/Riot/Modules/Common/Toasts/RoundedToastView.swift @@ -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 } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 78b5af425..030b7606e 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -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) { diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 4eddef5bc..f7b6649ca 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -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 diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 30ce90bc0..4a7fc7c24 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -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) { diff --git a/changelog.d/5605.change b/changelog.d/5605.change new file mode 100644 index 000000000..05f78b91d --- /dev/null +++ b/changelog.d/5605.change @@ -0,0 +1 @@ +Activity Indicators: Update loading and success messages when leaving room From 6d4b1885ab54c1753468e7fa814a8f82623d1a0c Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Mon, 21 Feb 2022 08:51:03 +0000 Subject: [PATCH 002/139] Use themed background color --- Riot/Modules/Common/Toasts/RoundedToastView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Common/Toasts/RoundedToastView.swift b/Riot/Modules/Common/Toasts/RoundedToastView.swift index 303bcbcec..49aacb6bf 100644 --- a/Riot/Modules/Common/Toasts/RoundedToastView.swift +++ b/Riot/Modules/Common/Toasts/RoundedToastView.swift @@ -98,7 +98,7 @@ class RoundedToastView: UIView, Themable { } func update(theme: Theme) { - backgroundColor = UIColor.white + backgroundColor = theme.colors.background stackView.arrangedSubviews.first?.tintColor = theme.colors.primaryContent label.font = theme.fonts.subheadline label.textColor = theme.colors.primaryContent From ddc4415cd1c2541a2805a7f55e871f349d6f2d7d Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 22 Feb 2022 15:06:45 +0300 Subject: [PATCH 003/139] Fix text color and font for regular reply events --- .../MatrixKit/Utils/EventFormatter/MXKEventFormatter.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index adb2b45af..ba4c6a2ea 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -1361,7 +1361,7 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)"; // For replies, look for the end of the parent message // This helps us insert the emote prefix in the right place - if (event.relatesTo.inReplyTo || (event.isInThread && !RiotSettings.shared.enableThreads)) + if (event.relatesTo.inReplyTo || (!RiotSettings.shared.enableThreads && event.isInThread)) { [attributedDisplayText enumerateAttribute:kMXKToolsBlockquoteMarkAttribute inRange:NSMakeRange(0, attributedDisplayText.length) @@ -1704,7 +1704,7 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)"; NSString *html = htmlString; // Special treatment for "In reply to" message - if (event.isReplyEvent || (event.isInThread && !RiotSettings.shared.enableThreads)) + if (event.isReplyEvent || (!RiotSettings.shared.enableThreads && event.isInThread)) { html = [self renderReplyTo:html withRoomState:roomState]; } @@ -2030,7 +2030,7 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)"; textColor = _errorTextColor; } // Check whether the message is highlighted. - else if (event.mxkIsHighlighted || (event.isInThread && !RiotSettings.shared.enableThreads && ![event.sender isEqualToString:mxSession.myUserId])) + else if (event.mxkIsHighlighted || (!RiotSettings.shared.enableThreads && event.isInThread && [event shouldBeHighlightedInSession:mxSession])) { textColor = _bingTextColor; } @@ -2094,7 +2094,7 @@ static NSString *const kHTMLATagRegexPattern = @"([^<]*)"; { font = _callNoticesTextFont; } - else if (event.mxkIsHighlighted || (event.isInThread && !RiotSettings.shared.enableThreads && ![event.sender isEqualToString:mxSession.myUserId])) + else if (event.mxkIsHighlighted || (!RiotSettings.shared.enableThreads && event.isInThread && [event shouldBeHighlightedInSession:mxSession])) { font = _bingTextFont; } From 5aba7e7caa6e78fcc0d8951299562a1e65564e1a Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 22 Feb 2022 15:08:42 +0300 Subject: [PATCH 004/139] Add changelog --- changelog.d/5552.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5552.bugfix diff --git a/changelog.d/5552.bugfix b/changelog.d/5552.bugfix new file mode 100644 index 000000000..d8281e2e9 --- /dev/null +++ b/changelog.d/5552.bugfix @@ -0,0 +1 @@ +MXKEventFormatter: Fix text color and font for regular reply events. From a1ce129193cf2e8ce336a30b9dc40e92f9c3bf78 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 22 Feb 2022 14:16:36 +0000 Subject: [PATCH 005/139] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index de6ddc959..76b125c78 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.8.2 -CURRENT_PROJECT_VERSION = 1.8.2 +MARKETING_VERSION = 1.8.3 +CURRENT_PROJECT_VERSION = 1.8.3 From 712d64204ce1f78fcc4f5a0c3a979c1d91319c77 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 22 Feb 2022 15:26:51 +0000 Subject: [PATCH 006/139] Rename Activity to UserIndicator --- .../Tests/UserIndicatorPresenterSpy.swift} | 2 +- .../Tests/UserIndicatorQueueTests.swift} | 32 ++++---- .../Tests/UserIndicatorTests.swift} | 74 +++++++++---------- .../UserIndicator.swift} | 26 +++---- .../UserIndicatorDismissal.swift} | 8 +- .../UserIndicatorPresentable.swift} | 8 +- .../UserIndicatorQueue.swift} | 30 ++++---- .../UserIndicatorRequest.swift} | 10 +-- Riot/Modules/Application/AppCoordinator.swift | 17 +++-- Riot/Modules/Application/AppNavigator.swift | 14 ++-- ....swift => AppUserIndicatorPresenter.swift} | 24 +++--- ...FullscreenActivityIndicatorPresenter.swift | 14 ++-- ...FullscreenLoadingIndicatorPresenter.swift} | 6 +- ...wift => ToastUserIndicatorPresenter.swift} | 6 +- .../Common/Recents/RecentsViewController.h | 6 +- .../Common/Recents/RecentsViewController.m | 16 ++-- Riot/Modules/TabBar/TabBarCoordinator.swift | 10 +-- 17 files changed, 152 insertions(+), 151 deletions(-) rename CommonKit/Source/{Activity/Tests/ActivityPresenterSpy.swift => UserIndicators/Tests/UserIndicatorPresenterSpy.swift} (92%) rename CommonKit/Source/{Activity/Tests/ActivityCenterTests.swift => UserIndicators/Tests/UserIndicatorQueueTests.swift} (56%) rename CommonKit/Source/{Activity/Tests/ActivityTests.swift => UserIndicators/Tests/UserIndicatorTests.swift} (56%) rename CommonKit/Source/{Activity/Activity.swift => UserIndicators/UserIndicator.swift} (65%) rename CommonKit/Source/{Activity/ActivityDismissal.swift => UserIndicators/UserIndicatorDismissal.swift} (69%) rename CommonKit/Source/{Activity/ActivityPresentable.swift => UserIndicators/UserIndicatorPresentable.swift} (65%) rename CommonKit/Source/{Activity/ActivityCenter.swift => UserIndicators/UserIndicatorQueue.swift} (52%) rename CommonKit/Source/{Activity/ActivityRequest.swift => UserIndicators/UserIndicatorRequest.swift} (65%) rename Riot/Modules/Common/ActivityIndicator/{AppActivityIndicatorPresenter.swift => AppUserIndicatorPresenter.swift} (60%) rename Riot/Modules/Common/ActivityPresenters/{FullscreenLoadingActivityPresenter.swift => FullscreenLoadingIndicatorPresenter.swift} (86%) rename Riot/Modules/Common/ActivityPresenters/{ToastActivityPresenter.swift => ToastUserIndicatorPresenter.swift} (87%) diff --git a/CommonKit/Source/Activity/Tests/ActivityPresenterSpy.swift b/CommonKit/Source/UserIndicators/Tests/UserIndicatorPresenterSpy.swift similarity index 92% rename from CommonKit/Source/Activity/Tests/ActivityPresenterSpy.swift rename to CommonKit/Source/UserIndicators/Tests/UserIndicatorPresenterSpy.swift index 2a0d03249..8be85ff9c 100644 --- a/CommonKit/Source/Activity/Tests/ActivityPresenterSpy.swift +++ b/CommonKit/Source/UserIndicators/Tests/UserIndicatorPresenterSpy.swift @@ -16,7 +16,7 @@ import Foundation -class ActivityPresenterSpy: ActivityPresentable { +class UserIndicatorPresenterSpy: UserIndicatorPresentable { var intel = [String]() func present() { diff --git a/CommonKit/Source/Activity/Tests/ActivityCenterTests.swift b/CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift similarity index 56% rename from CommonKit/Source/Activity/Tests/ActivityCenterTests.swift rename to CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift index a1e375851..0b937b8a8 100644 --- a/CommonKit/Source/Activity/Tests/ActivityCenterTests.swift +++ b/CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift @@ -17,34 +17,34 @@ import Foundation import XCTest -class ActivityCenterTests: XCTestCase { - var activities: [Activity]! - var center: ActivityCenter! +class UserIndicatorQueueTests: XCTestCase { + var indicators: [UserIndicator]! + var center: UserIndicatorQueue! override func setUp() { - activities = [] - center = ActivityCenter() + indicators = [] + center = UserIndicatorQueue() } - func makeRequest() -> ActivityRequest { - return ActivityRequest( - presenter: ActivityPresenterSpy(), + func makeRequest() -> UserIndicatorRequest { + return UserIndicatorRequest( + presenter: UserIndicatorPresenterSpy(), dismissal: .manual ) } - func testStartsActivityWhenAdded() { - let activity = center.add(makeRequest()) - XCTAssertEqual(activity.state, .executing) + func testStartsIndicatorWhenAdded() { + let indicator = center.add(makeRequest()) + XCTAssertEqual(indicator.state, .executing) } - func testSecondActivityIsPending() { - center.add(makeRequest()).store(in: &activities) - let activity = center.add(makeRequest()) - XCTAssertEqual(activity.state, .pending) + func testSecondIndicatorIsPending() { + center.add(makeRequest()).store(in: &indicators) + let indicator = center.add(makeRequest()) + XCTAssertEqual(indicator.state, .pending) } - func testSecondActivityIsExecutingWhenFirstCompleted() { + func testSecondIndicatorIsExecutingWhenFirstCompleted() { let first = center.add(makeRequest()) let second = center.add(makeRequest()) diff --git a/CommonKit/Source/Activity/Tests/ActivityTests.swift b/CommonKit/Source/UserIndicators/Tests/UserIndicatorTests.swift similarity index 56% rename from CommonKit/Source/Activity/Tests/ActivityTests.swift rename to CommonKit/Source/UserIndicators/Tests/UserIndicatorTests.swift index 11cc7eec2..e0722af8f 100644 --- a/CommonKit/Source/Activity/Tests/ActivityTests.swift +++ b/CommonKit/Source/UserIndicators/Tests/UserIndicatorTests.swift @@ -17,20 +17,20 @@ import Foundation import XCTest -class ActivityTests: XCTestCase { - var presenter: ActivityPresenterSpy! +class UserIndicatorTests: XCTestCase { + var presenter: UserIndicatorPresenterSpy! override func setUp() { super.setUp() - presenter = ActivityPresenterSpy() + presenter = UserIndicatorPresenterSpy() } - func makeActivity(dismissal: ActivityDismissal = .manual, callback: @escaping () -> Void = {}) -> Activity { - let request = ActivityRequest( + func makeIndicator(dismissal: UserIndicatorDismissal = .manual, callback: @escaping () -> Void = {}) -> UserIndicator { + let request = UserIndicatorRequest( presenter: presenter, dismissal: dismissal ) - return Activity( + return UserIndicator( request: request, completion: callback ) @@ -38,58 +38,58 @@ class ActivityTests: XCTestCase { // MARK: - State - func testNewActivityIsPending() { - let activity = makeActivity() - XCTAssertEqual(activity.state, .pending) + func testNewIndicatorIsPending() { + let indicator = makeIndicator() + XCTAssertEqual(indicator.state, .pending) } - func testStartedActivityIsExecuting() { - let activity = makeActivity() - activity.start() - XCTAssertEqual(activity.state, .executing) + func testStartedIndicatorIsExecuting() { + let indicator = makeIndicator() + indicator.start() + XCTAssertEqual(indicator.state, .executing) } - func testCancelledActivityIsCompleted() { - let activity = makeActivity() - activity.cancel() - XCTAssertEqual(activity.state, .completed) + func testCancelledIndicatorIsCompleted() { + let indicator = makeIndicator() + indicator.cancel() + XCTAssertEqual(indicator.state, .completed) } // MARK: - Presenter - func testStartingActivityPresentsUI() { - let activity = makeActivity() - activity.start() + func testStartingIndicatorPresentsUI() { + let indicator = makeIndicator() + indicator.start() XCTAssertEqual(presenter.intel, ["present()"]) } func testAllowStartingOnlyOnce() { - let activity = makeActivity() - activity.start() + let indicator = makeIndicator() + indicator.start() presenter.intel = [] - activity.start() + indicator.start() XCTAssertEqual(presenter.intel, []) } - func testCancellingActivityDismissesUI() { - let activity = makeActivity() - activity.start() + func testCancellingIndicatorDismissesUI() { + let indicator = makeIndicator() + indicator.start() presenter.intel = [] - activity.cancel() + indicator.cancel() XCTAssertEqual(presenter.intel, ["dismiss()"]) } func testAllowCancellingOnlyOnce() { - let activity = makeActivity() - activity.start() - activity.cancel() + let indicator = makeIndicator() + indicator.start() + indicator.cancel() presenter.intel = [] - activity.cancel() + indicator.cancel() XCTAssertEqual(presenter.intel, []) } @@ -98,9 +98,9 @@ class ActivityTests: XCTestCase { func testDismissAfterTimeout() { let interval: TimeInterval = 0.01 - let activity = makeActivity(dismissal: .timeout(interval)) + let indicator = makeIndicator(dismissal: .timeout(interval)) - activity.start() + indicator.start() let exp = expectation(description: "") DispatchQueue.main.asyncAfter(deadline: .now() + interval) { @@ -108,19 +108,19 @@ class ActivityTests: XCTestCase { } waitForExpectations(timeout: 1) - XCTAssertEqual(activity.state, .completed) + XCTAssertEqual(indicator.state, .completed) } // MARK: - Completion callback func testTriggersCallbackWhenCompleted() { var didComplete = false - let activity = makeActivity { + let indicator = makeIndicator { didComplete = true } - activity.start() + indicator.start() - activity.cancel() + indicator.cancel() XCTAssertTrue(didComplete) } diff --git a/CommonKit/Source/Activity/Activity.swift b/CommonKit/Source/UserIndicators/UserIndicator.swift similarity index 65% rename from CommonKit/Source/Activity/Activity.swift rename to CommonKit/Source/UserIndicators/UserIndicator.swift index 5c8eaac0b..72338013d 100644 --- a/CommonKit/Source/Activity/Activity.swift +++ b/CommonKit/Source/UserIndicators/UserIndicator.swift @@ -17,27 +17,27 @@ import Foundation import UIKit -/// An `Activity` represents the state of a temporary visual indicator, such as activity indicator, success notification or an error message. It does not directly manage the UI, instead it delegates to a `presenter` +/// A `UserIndicator` represents the state of a temporary visual indicator, such as loading spinner, success notification or an error message. It does not directly manage the UI, instead it delegates to a `presenter` /// whenever the UI should be shown or hidden. /// -/// 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. +/// More than one `UserIndicator` may be requested by the system at the same time (e.g. global syncing vs local refresh), +/// and the `UserIndicatorQueue` will ensure that only one indicator 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 +/// A client that requests an indicator can specify a default timeout after which the indicator is dismissed, or it has to be manually /// responsible for dismissing it via `cancel` method, or by deallocating itself. -public class Activity { +public class UserIndicator { public enum State { case pending case executing case completed } - private let request: ActivityRequest + private let request: UserIndicatorRequest private let completion: () -> Void public private(set) var state: State - public init(request: ActivityRequest, completion: @escaping () -> Void) { + public init(request: UserIndicatorRequest, completion: @escaping () -> Void) { self.request = request self.completion = completion @@ -66,10 +66,10 @@ public class Activity { } } - /// Cancel the activity, triggering any dismissal action / animation + /// Cancel the indicator, 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. + /// Note: clients can call this method directly, if they have access to the `UserIndicator`. + /// Once cancelled, `UserIndicatorQueue` will automatically start the next `UserIndicator` in the queue. public func cancel() { complete() } @@ -87,13 +87,13 @@ public class Activity { } } -public extension Activity { - func store(in collection: inout C) where C: RangeReplaceableCollection, C.Element == Activity { +public extension UserIndicator { + func store(in collection: inout C) where C: RangeReplaceableCollection, C.Element == UserIndicator { collection.append(self) } } -public extension Collection where Element == Activity { +public extension Collection where Element == UserIndicator { func cancelAll() { forEach { $0.cancel() diff --git a/CommonKit/Source/Activity/ActivityDismissal.swift b/CommonKit/Source/UserIndicators/UserIndicatorDismissal.swift similarity index 69% rename from CommonKit/Source/Activity/ActivityDismissal.swift rename to CommonKit/Source/UserIndicators/UserIndicatorDismissal.swift index 69405c579..874a4f1ed 100644 --- a/CommonKit/Source/Activity/ActivityDismissal.swift +++ b/CommonKit/Source/UserIndicators/UserIndicatorDismissal.swift @@ -16,10 +16,10 @@ import Foundation -/// Different ways in which an `Activity` can be dismissed -public enum ActivityDismissal { - /// The `Activity` will not manage the dismissal, but will expect the calling client to do so manually +/// Different ways in which a `UserIndicator` can be dismissed +public enum UserIndicatorDismissal { + /// The `UserIndicator` 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` + /// The `UserIndicator` will be automatically dismissed after `TimeInterval` case timeout(TimeInterval) } diff --git a/CommonKit/Source/Activity/ActivityPresentable.swift b/CommonKit/Source/UserIndicators/UserIndicatorPresentable.swift similarity index 65% rename from CommonKit/Source/Activity/ActivityPresentable.swift rename to CommonKit/Source/UserIndicators/UserIndicatorPresentable.swift index 5bb489fcc..00a90913e 100644 --- a/CommonKit/Source/Activity/ActivityPresentable.swift +++ b/CommonKit/Source/UserIndicators/UserIndicatorPresentable.swift @@ -16,10 +16,10 @@ import Foundation -/// A presenter associated with and called by an `Activity`, and responsible for the underlying view shown on the screen. -public protocol ActivityPresentable { - /// Called when the `Activity` is started (manually or by the `ActivityCenter`) +/// A presenter associated with and called by a `UserIndicator`, and responsible for the underlying view shown on the screen. +public protocol UserIndicatorPresentable { + /// Called when the `UserIndicator` is started (manually or by the `UserIndicatorQueue`) func present() - /// Called when the `Activity` is manually cancelled or completed + /// Called when the `UserIndicator` is manually cancelled or completed func dismiss() } diff --git a/CommonKit/Source/Activity/ActivityCenter.swift b/CommonKit/Source/UserIndicators/UserIndicatorQueue.swift similarity index 52% rename from CommonKit/Source/Activity/ActivityCenter.swift rename to CommonKit/Source/UserIndicators/UserIndicatorQueue.swift index 9d9e0c704..b66014c0f 100644 --- a/CommonKit/Source/Activity/ActivityCenter.swift +++ b/CommonKit/Source/UserIndicators/UserIndicatorQueue.swift @@ -16,11 +16,11 @@ import Foundation -/// A shared activity center with a single FIFO queue which will ensure only one activity is shown at a given time. +/// A FIFO queue which will ensure only one user indicator 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. -public class ActivityCenter { +/// `UserIndicatorQueue` offers a `shared` queue that can be used by any clients app-wide, but clients are also allowed +/// to create local `UserIndicatorQueue` if the context requres multiple simultaneous indicators. +public class UserIndicatorQueue { private class Weak { weak var element: T? init(_ element: T) { @@ -28,27 +28,27 @@ public class ActivityCenter { } } - public static let shared = ActivityCenter() - private var queue = [Weak]() + public static let shared = UserIndicatorQueue() + private var queue = [Weak]() - /// Add a new activity to the queue by providing a request. + /// Add a new indicator 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. - public func add(_ request: ActivityRequest) -> Activity { - let activity = Activity(request: request) { [weak self] in + /// The queue will start the indicator right away, if there are no currently running indicators, + /// otherwise the indicator will be put on hold. + public func add(_ request: UserIndicatorRequest) -> UserIndicator { + let indicator = UserIndicator(request: request) { [weak self] in self?.startNextIfIdle() } - queue.append(Weak(activity)) + queue.append(Weak(indicator)) startNextIfIdle() - return activity + return indicator } private func startNextIfIdle() { cleanup() - if let activity = queue.first?.element, activity.state == .pending { - activity.start() + if let indicator = queue.first?.element, indicator.state == .pending { + indicator.start() } } diff --git a/CommonKit/Source/Activity/ActivityRequest.swift b/CommonKit/Source/UserIndicators/UserIndicatorRequest.swift similarity index 65% rename from CommonKit/Source/Activity/ActivityRequest.swift rename to CommonKit/Source/UserIndicators/UserIndicatorRequest.swift index f009fc211..83e882c0e 100644 --- a/CommonKit/Source/Activity/ActivityRequest.swift +++ b/CommonKit/Source/UserIndicators/UserIndicatorRequest.swift @@ -16,12 +16,12 @@ import Foundation -/// A request used to create an underlying `Activity`, allowing clients to only specify the visual aspects of an activity. -public struct ActivityRequest { - internal let presenter: ActivityPresentable - internal let dismissal: ActivityDismissal +/// A request used to create an underlying `UserIndicator`, allowing clients to only specify the visual aspects of an indicator. +public struct UserIndicatorRequest { + internal let presenter: UserIndicatorPresentable + internal let dismissal: UserIndicatorDismissal - public init(presenter: ActivityPresentable, dismissal: ActivityDismissal) { + public init(presenter: UserIndicatorPresentable, dismissal: UserIndicatorDismissal) { self.presenter = presenter self.dismissal = dismissal } diff --git a/Riot/Modules/Application/AppCoordinator.swift b/Riot/Modules/Application/AppCoordinator.swift index a30a69e68..93ae25fe5 100755 --- a/Riot/Modules/Application/AppCoordinator.swift +++ b/Riot/Modules/Application/AppCoordinator.swift @@ -322,39 +322,40 @@ fileprivate class AppNavigator: AppNavigatorProtocol { self.appCoordinator.navigate(to: destination) } - func addAppActivity(_ type: AppActivityType) -> Activity { - let request = activityRequest(for: type) - return ActivityCenter.shared.add(request) + func addUserIndicator(_ type: AppUserIndicatorType) -> UserIndicator { + let request = userIndicatorRequest(for: type) + return UserIndicatorQueue.shared.add(request) } // MARK: - Private - private func activityRequest(for type: AppActivityType) -> ActivityRequest { + private func userIndicatorRequest(for type: AppUserIndicatorType) -> UserIndicatorRequest { switch type { case let .loading(label): - let presenter = ToastActivityPresenter( + let presenter = ToastUserIndicatorPresenter( viewState: .init( style: .loading, label: label ), navigationController: appNavigationVC ) - return ActivityRequest( + return UserIndicatorRequest( presenter: presenter, dismissal: .manual ) case let .success(label): - let presenter = ToastActivityPresenter( + let presenter = ToastUserIndicatorPresenter( viewState: .init( style: .success, label: label ), navigationController: appNavigationVC ) - return ActivityRequest( + return UserIndicatorRequest( presenter: presenter, dismissal: .timeout(1.5) ) } } } + diff --git a/Riot/Modules/Application/AppNavigator.swift b/Riot/Modules/Application/AppNavigator.swift index 030c25c04..dd0eff1eb 100644 --- a/Riot/Modules/Application/AppNavigator.swift +++ b/Riot/Modules/Application/AppNavigator.swift @@ -17,8 +17,8 @@ import Foundation import CommonKit -/// Type of activity to be shown in the app navigator -enum AppActivityType { +/// Type of indicator to be shown in the app navigator +enum AppUserIndicatorType { /// Loading toast with custom label case loading(String) @@ -37,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 new activity, such as loading indicator or a success message, to an app-wide queue of other activities + /// Add new indicator, such as loading spinner or a success message, to an app-wide queue of other indicators /// - /// 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. + /// If the queue is empty, the indicator will be displayed immediately, otherwise it will be pending + /// until the previously added indicator have completed / been cancelled. /// - /// To remove an activity indicator, cancel or deallocate the returned `Activity` - func addAppActivity(_ type: AppActivityType) -> Activity + /// To remove an indicator, cancel or deallocate the returned `UserIndicator` + func addUserIndicator(_ type: AppUserIndicatorType) -> UserIndicator } diff --git a/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/AppUserIndicatorPresenter.swift similarity index 60% rename from Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift rename to Riot/Modules/Common/ActivityIndicator/AppUserIndicatorPresenter.swift index d3bd3de28..245b5e323 100644 --- a/Riot/Modules/Common/ActivityIndicator/AppActivityIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityIndicator/AppUserIndicatorPresenter.swift @@ -19,16 +19,16 @@ 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. +/// Presenter which displays loading spinners using app-wide `AppNavigator`, thus displaying them in a unified way, +/// and `UserIndicatorCenter`/`UserIndicator`, which ensures that only one indicator is shown at a given time. /// -/// Note: clients can skip using `AppActivityIndicatorPresenter` and instead coordiinate with `AppNavigatorProtocol` directly. +/// Note: clients can skip using `AppUserIndicatorPresenter` and instead coordinate 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 { +@objc final class AppUserIndicatorPresenter: NSObject, ActivityIndicatorPresenterType { private let appNavigator: AppNavigatorProtocol - private var loadingActivity: Activity? - private var otherActivities = [Activity]() + private var loadingIndicator: UserIndicator? + private var otherIndicators = [UserIndicator]() init(appNavigator: AppNavigatorProtocol) { self.appNavigator = appNavigator @@ -39,24 +39,24 @@ import CommonKit } @objc func presentActivityIndicator(label: String) { - guard loadingActivity == nil || loadingActivity?.state == .completed else { + guard loadingIndicator == nil || loadingIndicator?.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. + // so there is no reason to keep adding new indiciators if there is one already showing. return } - loadingActivity = appNavigator.addAppActivity(.loading(label)) + loadingIndicator = appNavigator.addUserIndicator(.loading(label)) } @objc func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)?) { - loadingActivity = nil + loadingIndicator = nil } func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)?) { - MXLog.error("[AppActivityIndicatorPresenter] Shared activity indicator does not support presenting from custom views") + MXLog.error("[AppUserIndicatorPresenter] Shared indicator presenter does not support presenting from custom views") } @objc func presentSuccess(label: String) { - appNavigator.addAppActivity(.success(label)).store(in: &otherActivities) + appNavigator.addUserIndicator(.success(label)).store(in: &otherIndicators) } } diff --git a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift index 7656e15fb..bf8722202 100644 --- a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift @@ -18,8 +18,8 @@ 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`. +/// Presenter which displays fullscreen loading spinners, and conforming to legacy `ActivityIndicatorPresenterType`, +/// but interally wrapping an `UserIndicatorPresenter` which is used in conjuction with `UserIndicator` and `UserIndicatorQueue`. /// /// 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 @@ -27,7 +27,7 @@ import UIKit @objc final class FullscreenActivityIndicatorPresenter: NSObject, ActivityIndicatorPresenterType { private let label: String private weak var viewController: UIViewController? - private var activity: Activity? + private var indicator: UserIndicator? init(label: String, on viewController: UIViewController) { self.label = label @@ -39,15 +39,15 @@ import UIKit return } - let request = ActivityRequest( - presenter: FullscreenLoadingActivityPresenter(label: label, on: vc), + let request = UserIndicatorRequest( + presenter: FullscreenLoadingIndicatorPresenter(label: label, on: vc), dismissal: .manual ) - activity = ActivityCenter.shared.add(request) + indicator = UserIndicatorQueue.shared.add(request) } @objc func removeCurrentActivityIndicator(animated: Bool, completion: (() -> Void)?) { - activity?.cancel() + indicator?.cancel() } } diff --git a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift similarity index 86% rename from Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift rename to Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift index 0ca4cb473..8ca7fb026 100644 --- a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingActivityPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift @@ -18,9 +18,9 @@ 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 { +/// A `UserIndicatorPresentable` responsible for showing / hiding a full-screen loading view that obscures (and thus disables) all other controls. +/// It is managed by a `UserIndicator`, meaning the `present` and `dismiss` methods will be called when the parent `UserIndicator` starts or completes. +class FullscreenLoadingIndicatorPresenter: UserIndicatorPresentable { private let label: String private weak var viewController: UIViewController? private weak var view: UIView? diff --git a/Riot/Modules/Common/ActivityPresenters/ToastActivityPresenter.swift b/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift similarity index 87% rename from Riot/Modules/Common/ActivityPresenters/ToastActivityPresenter.swift rename to Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift index f0bff4c17..f975cd651 100644 --- a/Riot/Modules/Common/ActivityPresenters/ToastActivityPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift @@ -19,9 +19,9 @@ import UIKit import CommonKit import MatrixSDK -/// 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 { +/// A `UserIndicatorPresentable` responsible for showing / hiding a toast view for loading spinners or success messages. +/// It is managed by an `UserIndicator`, meaning the `present` and `dismiss` methods will be called when the parent `UserIndicator` starts or completes. +class ToastUserIndicatorPresenter: UserIndicatorPresentable { private let viewState: RoundedToastView.ViewState private weak var navigationController: UINavigationController? private weak var view: UIView? diff --git a/Riot/Modules/Common/Recents/RecentsViewController.h b/Riot/Modules/Common/Recents/RecentsViewController.h index 00f728d24..9c29a28f8 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.h +++ b/Riot/Modules/Common/Recents/RecentsViewController.h @@ -19,7 +19,7 @@ @class RootTabEmptyView; @class AnalyticsScreenTimer; -@class AppActivityIndicatorPresenter; +@class AppUserIndicatorPresenter; /** Notification to be posted when recents data is ready. Notification object will be the RecentsViewController instance. @@ -98,9 +98,9 @@ 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 + Presenter for displaying app-wide user indicators. If not set, the view controller will use legacy activity indicators */ -@property (nonatomic, strong) AppActivityIndicatorPresenter *activityPresenter; +@property (nonatomic, strong) AppUserIndicatorPresenter *userIndicatorPresenter; /** 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 ad09e064d..722b706a2 100644 --- a/Riot/Modules/Common/Recents/RecentsViewController.m +++ b/Riot/Modules/Common/Recents/RecentsViewController.m @@ -1295,7 +1295,7 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro { typeof(self) self = weakSelf; [self stopActivityIndicator]; - [self.activityPresenter presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]]; + [self.userIndicatorPresenter presentSuccessWithLabel:[VectorL10n roomParticipantsLeaveSuccess]]; // Force table refresh [self cancelEditionMode:YES]; } @@ -2410,28 +2410,28 @@ NSString *const RecentsViewControllerDataReadyNotification = @"RecentsViewContro #pragma mark - Activity Indicator - (BOOL)providesCustomActivityIndicator { - return self.activityPresenter != nil; + return self.userIndicatorPresenter != nil; } - (void)startActivityIndicatorWithLabel:(NSString *)label { - if (self.activityPresenter) { - [self.activityPresenter presentActivityIndicatorWithLabel:label]; + if (self.userIndicatorPresenter) { + [self.userIndicatorPresenter presentActivityIndicatorWithLabel:label]; } else { [super startActivityIndicator]; } } - (void)startActivityIndicator { - if (self.activityPresenter) { - [self.activityPresenter presentActivityIndicator]; + if (self.userIndicatorPresenter) { + [self.userIndicatorPresenter presentActivityIndicator]; } else { [super startActivityIndicator]; } } - (void)stopActivityIndicator { - if (self.activityPresenter) { - [self.activityPresenter removeCurrentActivityIndicatorWithAnimated:YES completion:nil]; + if (self.userIndicatorPresenter) { + [self.userIndicatorPresenter removeCurrentActivityIndicatorWithAnimated:YES completion:nil]; } else { [super stopActivityIndicator]; } diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 4a7fc7c24..4fd95c251 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -54,7 +54,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { return self.navigationRouter.modules.last is MasterTabBarController } - private var activities = [Activity]() + private var indicators = [UserIndicator]() // MARK: Public @@ -77,7 +77,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { } deinit { - activities.cancelAll() + indicators.cancelAll() } // MARK: - Public methods @@ -235,7 +235,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { homeViewController.accessibilityLabel = VectorL10n.titleHome if BuildSettings.appActivityIndicators { - homeViewController.activityPresenter = AppActivityIndicatorPresenter(appNavigator: parameters.appNavigator) + homeViewController.userIndicatorPresenter = AppUserIndicatorPresenter(appNavigator: parameters.appNavigator) } let wrapperViewController = HomeViewControllerWithBannerWrapperViewController(viewController: homeViewController) @@ -711,8 +711,8 @@ extension TabBarCoordinator: RoomCoordinatorDelegate { self.resetSplitViewDetails() if BuildSettings.appActivityIndicators { parameters.appNavigator - .addAppActivity(.success(VectorL10n.roomParticipantsLeaveSuccess)) - .store(in: &activities) + .addUserIndicator(.success(VectorL10n.roomParticipantsLeaveSuccess)) + .store(in: &indicators) } } From f85e0ccea1ffe96466d946b7c2b5e3d0d5244315 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 22 Feb 2022 16:21:11 +0000 Subject: [PATCH 007/139] Extract toast view state --- ...FullscreenActivityIndicatorPresenter.swift | 2 +- .../ToastUserIndicatorPresenter.swift | 4 +-- .../Common/Toasts/RoundedToastView.swift | 25 +++++------------ .../Common/Toasts/ToastViewState.swift | 27 +++++++++++++++++++ 4 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 Riot/Modules/Common/Toasts/ToastViewState.swift diff --git a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift index bf8722202..b5f7cfb14 100644 --- a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift @@ -21,7 +21,7 @@ import UIKit /// Presenter which displays fullscreen loading spinners, and conforming to legacy `ActivityIndicatorPresenterType`, /// but interally wrapping an `UserIndicatorPresenter` which is used in conjuction with `UserIndicator` and `UserIndicatorQueue`. /// -/// Note: clients can skip using `FullscreenActivityIndicatorPresenter` and instead coordiinate with `AppNavigatorProtocol` directly. +/// Note: clients can skip using `FullscreenActivityIndicatorPresenter` and instead coordinate 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 { diff --git a/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift b/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift index f975cd651..281bb92af 100644 --- a/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift @@ -22,11 +22,11 @@ import MatrixSDK /// A `UserIndicatorPresentable` responsible for showing / hiding a toast view for loading spinners or success messages. /// It is managed by an `UserIndicator`, meaning the `present` and `dismiss` methods will be called when the parent `UserIndicator` starts or completes. class ToastUserIndicatorPresenter: UserIndicatorPresentable { - private let viewState: RoundedToastView.ViewState + private let viewState: ToastViewState private weak var navigationController: UINavigationController? private weak var view: UIView? - init(viewState: RoundedToastView.ViewState, navigationController: UINavigationController) { + init(viewState: ToastViewState, navigationController: UINavigationController) { self.viewState = viewState self.navigationController = navigationController } diff --git a/Riot/Modules/Common/Toasts/RoundedToastView.swift b/Riot/Modules/Common/Toasts/RoundedToastView.swift index 49aacb6bf..927c608f7 100644 --- a/Riot/Modules/Common/Toasts/RoundedToastView.swift +++ b/Riot/Modules/Common/Toasts/RoundedToastView.swift @@ -35,13 +35,13 @@ class RoundedToastView: UIView, Themable { return indicator }() - private lazy var imagView: UIImageView = { + private lazy var imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit imageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ imageView.widthAnchor.constraint(equalToConstant: Constants.imageViewSize), - imageView.heightAnchor.constraint(equalToConstant: Constants.imageViewSize), + imageView.heightAnchor.constraint(equalToConstant: Constants.imageViewSize) ]) return imageView }() @@ -57,7 +57,7 @@ class RoundedToastView: UIView, Themable { return UILabel() }() - init(viewState: ViewState) { + init(viewState: ToastViewState) { super.init(frame: .zero) setup(viewState: viewState) } @@ -66,7 +66,7 @@ class RoundedToastView: UIView, Themable { fatalError("init(coder:) has not been implemented") } - private func setup(viewState: ViewState) { + private func setup(viewState: ToastViewState) { setupLayer() setupStackView() stackView.addArrangedSubview(toastView(for: viewState.style)) @@ -104,24 +104,13 @@ class RoundedToastView: UIView, Themable { label.textColor = theme.colors.primaryContent } - private func toastView(for style: Style) -> UIView { + private func toastView(for style: ToastViewState.Style) -> UIView { switch style { case .loading: return activityIndicator case .success: - imagView.image = Asset.Images.checkmark.image - return imagView + imageView.image = Asset.Images.checkmark.image + return imageView } } } - -extension RoundedToastView { - enum Style { - case loading - case success - } - struct ViewState { - let style: Style - let label: String - } -} diff --git a/Riot/Modules/Common/Toasts/ToastViewState.swift b/Riot/Modules/Common/Toasts/ToastViewState.swift new file mode 100644 index 000000000..e241d4645 --- /dev/null +++ b/Riot/Modules/Common/Toasts/ToastViewState.swift @@ -0,0 +1,27 @@ +// +// 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 + +struct ToastViewState { + enum Style { + case loading + case success + } + + let style: Style + let label: String +} From c9c84382503f90b9b2f7c1905eda7258a33406b1 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 22 Feb 2022 16:44:22 +0000 Subject: [PATCH 008/139] Rename properties and arguments --- .../Tests/UserIndicatorQueueTests.swift | 14 ++++++------- Config/BuildSettings.swift | 2 +- ...FullscreenActivityIndicatorPresenter.swift | 6 +++--- .../FullscreenLoadingIndicatorPresenter.swift | 20 +++++++++---------- .../RoomInfoListViewController.swift | 4 ++-- Riot/Modules/TabBar/TabBarCoordinator.swift | 8 ++------ 6 files changed, 25 insertions(+), 29 deletions(-) diff --git a/CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift b/CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift index 0b937b8a8..50c2e458d 100644 --- a/CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift +++ b/CommonKit/Source/UserIndicators/Tests/UserIndicatorQueueTests.swift @@ -19,11 +19,11 @@ import XCTest class UserIndicatorQueueTests: XCTestCase { var indicators: [UserIndicator]! - var center: UserIndicatorQueue! + var queue: UserIndicatorQueue! override func setUp() { indicators = [] - center = UserIndicatorQueue() + queue = UserIndicatorQueue() } func makeRequest() -> UserIndicatorRequest { @@ -34,19 +34,19 @@ class UserIndicatorQueueTests: XCTestCase { } func testStartsIndicatorWhenAdded() { - let indicator = center.add(makeRequest()) + let indicator = queue.add(makeRequest()) XCTAssertEqual(indicator.state, .executing) } func testSecondIndicatorIsPending() { - center.add(makeRequest()).store(in: &indicators) - let indicator = center.add(makeRequest()) + queue.add(makeRequest()).store(in: &indicators) + let indicator = queue.add(makeRequest()) XCTAssertEqual(indicator.state, .pending) } func testSecondIndicatorIsExecutingWhenFirstCompleted() { - let first = center.add(makeRequest()) - let second = center.add(makeRequest()) + let first = queue.add(makeRequest()) + let second = queue.add(makeRequest()) first.cancel() diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 3eb9c33a6..fe9679106 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -211,7 +211,7 @@ 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 { + static var useAppUserIndicators: Bool { #if DEBUG return true #else diff --git a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift index b5f7cfb14..610f56f13 100644 --- a/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityIndicator/FullscreenActivityIndicatorPresenter.swift @@ -29,18 +29,18 @@ import UIKit private weak var viewController: UIViewController? private var indicator: UserIndicator? - init(label: String, on viewController: UIViewController) { + init(label: String, viewController: UIViewController) { self.label = label self.viewController = viewController } func presentActivityIndicator(on view: UIView, animated: Bool, completion: (() -> Void)?) { - guard let vc = viewController else { + guard let viewController = viewController else { return } let request = UserIndicatorRequest( - presenter: FullscreenLoadingIndicatorPresenter(label: label, on: vc), + presenter: FullscreenLoadingIndicatorPresenter(label: label, viewController: viewController), dismissal: .manual ) diff --git a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift index 8ca7fb026..da480cbb5 100644 --- a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift @@ -25,18 +25,18 @@ class FullscreenLoadingIndicatorPresenter: UserIndicatorPresentable { private weak var viewController: UIViewController? private weak var view: UIView? - init(label: String, on viewController: UIViewController) { + init(label: String, 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 + var presentingController: UIViewController? = viewController + while presentingController?.navigationController != nil { + presentingController = presentingController?.navigationController } - guard let presentingVC = vc else { + guard let presentingController = presentingController else { return } @@ -45,12 +45,12 @@ class FullscreenLoadingIndicatorPresenter: UserIndicatorPresentable { self.view = view view.translatesAutoresizingMaskIntoConstraints = false - presentingVC.view.addSubview(view) + presentingController.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.topAnchor.constraint(equalTo: presentingController.view.topAnchor), + view.bottomAnchor.constraint(equalTo: presentingController.view.bottomAnchor), + view.leadingAnchor.constraint(equalTo: presentingController.view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: presentingController.view.trailingAnchor) ]) view.alpha = 0 diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift index 030b7606e..4f9cd8c47 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift @@ -116,10 +116,10 @@ final class RoomInfoListViewController: UIViewController { // Do any additional setup after loading the view. self.setupViews() - if BuildSettings.appActivityIndicators { + if BuildSettings.useAppUserIndicators { self.activityPresenter = FullscreenActivityIndicatorPresenter( label: VectorL10n.roomParticipantsLeaveProcessing, - on: self + viewController: self ) } else { self.activityPresenter = ActivityIndicatorPresenter() diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 4fd95c251..6342ccda5 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -76,10 +76,6 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { self.activityIndicatorPresenter = ActivityIndicatorPresenter() } - deinit { - indicators.cancelAll() - } - // MARK: - Public methods func start() { @@ -234,7 +230,7 @@ final class TabBarCoordinator: NSObject, TabBarCoordinatorType { homeViewController.tabBarItem.image = homeViewController.tabBarItem.image homeViewController.accessibilityLabel = VectorL10n.titleHome - if BuildSettings.appActivityIndicators { + if BuildSettings.useAppUserIndicators { homeViewController.userIndicatorPresenter = AppUserIndicatorPresenter(appNavigator: parameters.appNavigator) } @@ -709,7 +705,7 @@ 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 { + if BuildSettings.useAppUserIndicators { parameters.appNavigator .addUserIndicator(.success(VectorL10n.roomParticipantsLeaveSuccess)) .store(in: &indicators) From fda96626e6ef7aa7dcf48cdae5196867ead78dfd Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 22 Feb 2022 16:52:48 +0000 Subject: [PATCH 009/139] Revert presenter dismissal --- .../FullscreenLoadingIndicatorPresenter.swift | 15 ++++++++++----- .../ToastUserIndicatorPresenter.swift | 18 +++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift index da480cbb5..e5488a19a 100644 --- a/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/FullscreenLoadingIndicatorPresenter.swift @@ -54,7 +54,6 @@ class FullscreenLoadingIndicatorPresenter: UserIndicatorPresentable { ]) view.alpha = 0 - CATransaction.commit() UIView.animate(withDuration: 0.2) { view.alpha = 1 } @@ -65,10 +64,16 @@ class FullscreenLoadingIndicatorPresenter: UserIndicatorPresentable { return } - UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState) { - view.alpha = 0 - } completion: { _ in - view.removeFromSuperview() + // 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 + } completion: { _ in + view.removeFromSuperview() + } } } } diff --git a/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift b/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift index 281bb92af..6f7973b3d 100644 --- a/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift +++ b/Riot/Modules/Common/ActivityPresenters/ToastUserIndicatorPresenter.swift @@ -48,9 +48,7 @@ class ToastUserIndicatorPresenter: UserIndicatorPresentable { ]) view.alpha = 0 - CATransaction.flush() view.transform = .init(translationX: 0, y: 5) - UIView.animate(withDuration: 0.2) { view.alpha = 1 view.transform = .identity @@ -62,11 +60,17 @@ class ToastUserIndicatorPresenter: UserIndicatorPresentable { return } - UIView.animate(withDuration: 0.2, delay: 0, options: .beginFromCurrentState) { - view.alpha = 0 - view.transform = .init(translationX: 0, y: -5) - } completion: { _ in - view.removeFromSuperview() + // 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() + } } } } From d3056d0ad0003debc37decf6c016563a312bb90a Mon Sep 17 00:00:00 2001 From: MaximeE Date: Wed, 23 Feb 2022 11:03:22 +0100 Subject: [PATCH 010/139] 3779: show the input tool bar on timeline not live --- .../MatrixKit/Models/Room/MXKRoomDataSource.h | 4 +++ .../MatrixKit/Models/Room/MXKRoomDataSource.m | 8 +++--- Riot/Modules/Room/MXKRoomViewController.h | 5 ++++ Riot/Modules/Room/MXKRoomViewController.m | 8 +++--- Riot/Modules/Room/RoomViewController.m | 25 ++++++++++++++++--- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h index 70bfe77d2..6bb9e5042 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.h @@ -454,12 +454,14 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey; @param eventIdToReply the id of event to reply. @param text the text to send. + @param actualData the data source actually. It's use to retrieve event to reply. @param success A block object called when the operation succeeds. It returns the event id of the event generated on the homeserver @param failure A block object called when the operation fails. */ - (void)sendReplyToEventWithId:(NSString*)eventIdToReply withTextMessage:(NSString *)text + actualRoomDataSource:(MXKRoomDataSource *)actualData success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure; @@ -797,12 +799,14 @@ extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey; @param eventId The eventId of event to replace. @param text The new message text. + @param actualData the data source actually. It's use to retrieve event to replace. @param success A block object called when the operation succeeds. It returns the event id of the event generated on the homeserver. @param failure A block object called when the operation fails. */ - (void)replaceTextMessageForEventWithId:(NSString *)eventId withTextMessage:(NSString *)text + actualRoomDataSource:(MXKRoomDataSource *)actualData success:(void (^)(NSString *eventId))success failure:(void (^)(NSError *error))failure; diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m index 146fa084d..49e62b8a0 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m @@ -1872,10 +1872,11 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { - (void)sendReplyToEventWithId:(NSString*)eventIdToReply withTextMessage:(NSString *)text + actualRoomDataSource:(MXKRoomDataSource *)actualData success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure { - MXEvent *eventToReply = [self eventWithEventId:eventIdToReply]; + MXEvent *eventToReply = [actualData eventWithEventId:eventIdToReply]; __block MXEvent *localEchoEvent = nil; @@ -4285,11 +4286,12 @@ typedef NS_ENUM (NSUInteger, MXKRoomDataSourceError) { } - (void)replaceTextMessageForEventWithId:(NSString*)eventId - withTextMessage:(NSString *)text + withTextMessage:(NSString *)text + actualRoomDataSource:(MXKRoomDataSource *)actualData success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure { - MXEvent *event = [self eventWithEventId:eventId]; + MXEvent *event = [actualData eventWithEventId:eventId]; NSString *sanitizedText = [self sanitizedMessageText:text]; NSString *formattedText = [self htmlMessageFromSanitizedText:sanitizedText]; diff --git a/Riot/Modules/Room/MXKRoomViewController.h b/Riot/Modules/Room/MXKRoomViewController.h index 5eb96dd3e..f01984755 100644 --- a/Riot/Modules/Room/MXKRoomViewController.h +++ b/Riot/Modules/Room/MXKRoomViewController.h @@ -102,6 +102,11 @@ typedef NS_ENUM(NSUInteger, MXKRoomViewControllerJoinRoomResult) { */ @property (nonatomic, readonly) MXKRoomDataSource *roomDataSource; +/** + The data source associated to live timeline, in the case the view controller show timeline not live. + */ +@property (nonatomic) MXKRoomDataSource *roomDataSourceLive; + /** Flag indicating if this instance has the memory ownership of its `roomDataSource`. If YES, it will release it on [self destroy] call; diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m index 1123358e1..65441804f 100644 --- a/Riot/Modules/Room/MXKRoomViewController.m +++ b/Riot/Modules/Room/MXKRoomViewController.m @@ -680,9 +680,9 @@ if (dataSource) { - if (!dataSource.isLive || dataSource.isPeeking) + if (dataSource.isPeeking) { - // Remove the input toolbar if the displayed timeline is not a live one or in case of peeking. + // Remove the input toolbar in case of peeking. // We do not let the user type message in this case. [self setRoomInputToolbarViewClass:nil]; } @@ -1085,9 +1085,9 @@ inputToolbarView = nil; } - if (roomDataSource && (!roomDataSource.isLive || roomDataSource.isPeeking)) + if (roomDataSource && roomDataSource.isPeeking) { - // Do not show the input toolbar if the displayed timeline is not a live one, or in case of peeking. + // Do not show the input toolbar if the displayed timeline in case of peeking. // We do not let the user type message in this case. roomInputToolbarViewClass = nil; } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 331c07ba4..2d5cd5aa1 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1254,16 +1254,23 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; - (void)sendTextMessage:(NSString*)msgTxt { + // If the event occur on timeline not live, use the live data source to resolve event + BOOL isLive = self.roomDataSource.isLive; + if (isLive == NO) { + [self setupRoomDataSourceLive]; + } + MXKRoomDataSource *roomDataSource = isLive ? self.roomDataSource : self.roomDataSourceLive; + if (self.inputToolBarSendMode == RoomInputToolbarViewSendModeReply && customizedRoomDataSource.selectedEventId) { - [self.roomDataSource sendReplyToEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:nil failure:^(NSError *error) { + [roomDataSource sendReplyToEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt actualRoomDataSource:self.roomDataSource success:nil failure:^(NSError *error) { // Just log the error. The message will be displayed in red in the room history MXLogDebug(@"[MXKRoomViewController] sendTextMessage failed."); }]; } else if (self.inputToolBarSendMode == RoomInputToolbarViewSendModeEdit && customizedRoomDataSource.selectedEventId) { - [self.roomDataSource replaceTextMessageForEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt success:nil failure:^(NSError *error) { + [roomDataSource replaceTextMessageForEventWithId:customizedRoomDataSource.selectedEventId withTextMessage:msgTxt actualRoomDataSource:self.roomDataSource success:nil failure:^(NSError *error) { // Just log the error. The message will be displayed in red MXLogDebug(@"[MXKRoomViewController] sendTextMessage failed."); }]; @@ -1271,7 +1278,7 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; else { // Let the datasource send it and manage the local echo - [self.roomDataSource sendTextMessage:msgTxt success:nil failure:^(NSError *error) + [roomDataSource sendTextMessage:msgTxt success:nil failure:^(NSError *error) { // Just log the error. The message will be displayed in red in the room history MXLogDebug(@"[MXKRoomViewController] sendTextMessage failed."); @@ -2303,6 +2310,18 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; [suggestionsViewController didMoveToParentViewController:self]; } +- (void)setupRoomDataSourceLive +{ + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; + + [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId + create:YES + onComplete:^(MXKRoomDataSource *roomDataSource) { + self.roomDataSourceLive = roomDataSource; + [self.roomDataSourceLive finalizeInitialization]; + }]; +} + #pragma mark - Jitsi - (void)showJitsiCallWithWidget:(Widget*)widget From 6d9516cff355da4ee6342c851a2c683b81ad53b4 Mon Sep 17 00:00:00 2001 From: MaximeE Date: Wed, 23 Feb 2022 11:17:42 +0100 Subject: [PATCH 011/139] 3779: Changelog --- changelog.d/3779.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3779.bugfix diff --git a/changelog.d/3779.bugfix b/changelog.d/3779.bugfix new file mode 100644 index 000000000..695455ebe --- /dev/null +++ b/changelog.d/3779.bugfix @@ -0,0 +1 @@ +Input Tool Bar: Show it when you jump to an old message (last unread message, direct link or from unified search) \ No newline at end of file From 3fd31f3c7b30867cfd72d644d78d77f43d2c6acc Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 23 Feb 2022 11:41:05 +0000 Subject: [PATCH 012/139] Use protocol to get title from child VCs. --- .../Communities/GroupsViewController.m | 11 +++++++--- .../Favorites/FavouritesViewController.m | 11 +++++++--- Riot/Modules/Home/HomeViewController.m | 2 -- ...ollerWithBannerWrapperViewController.swift | 8 ++++++- Riot/Modules/People/PeopleViewController.m | 11 +++++++--- Riot/Modules/Rooms/RoomsViewController.m | 11 +++++++--- Riot/Modules/TabBar/MasterTabBarController.m | 17 +++++++++++++-- .../MasterTabBarItemDisplayProtocol.swift | 21 +++++++++++++++++++ 8 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 Riot/Modules/TabBar/MasterTabBarItemDisplayProtocol.swift diff --git a/Riot/Modules/Communities/GroupsViewController.m b/Riot/Modules/Communities/GroupsViewController.m index 306a4e55c..fb1ac9552 100644 --- a/Riot/Modules/Communities/GroupsViewController.m +++ b/Riot/Modules/Communities/GroupsViewController.m @@ -21,7 +21,7 @@ #import "GeneratedInterface-Swift.h" -@interface GroupsViewController () +@interface GroupsViewController () { // Tell whether a groups refresh is pending (suspended during editing mode). BOOL isRefreshPending; @@ -224,8 +224,6 @@ [self scrollToTop:YES]; }]; - - [AppDelegate theDelegate].masterTabBarController.navigationItem.title = [VectorL10n titleGroups]; [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; } @@ -644,4 +642,11 @@ [self.groupsSearchBar setShowsCancelButton:NO animated:NO]; } +#pragma mark - MasterTabBarItemDisplayProtocol + +- (NSString *)masterTabBarItemTitle +{ + return [VectorL10n titleGroups]; +} + @end diff --git a/Riot/Modules/Favorites/FavouritesViewController.m b/Riot/Modules/Favorites/FavouritesViewController.m index 62ef8cd8f..da680c7ee 100644 --- a/Riot/Modules/Favorites/FavouritesViewController.m +++ b/Riot/Modules/Favorites/FavouritesViewController.m @@ -19,7 +19,7 @@ #import "RecentsDataSource.h" #import "GeneratedInterface-Swift.h" -@interface FavouritesViewController () +@interface FavouritesViewController () { RecentsDataSource *recentsDataSource; } @@ -62,8 +62,6 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - [AppDelegate theDelegate].masterTabBarController.navigationItem.title = [VectorL10n titleFavourites]; [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; if (recentsDataSource) @@ -163,4 +161,11 @@ } } +#pragma mark - MasterTabBarItemDisplayProtocol + +- (NSString *)masterTabBarItemTitle +{ + return [VectorL10n titleFavourites]; +} + @end diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index e94b4c472..95a489e3b 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -103,8 +103,6 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - [AppDelegate theDelegate].masterTabBarController.navigationItem.title = [VectorL10n titleHome]; [ThemeService.shared.theme applyStyleOnNavigationBar:[AppDelegate theDelegate].masterTabBarController.navigationController.navigationBar]; diff --git a/Riot/Modules/Home/VersionCheck/HomeViewControllerWithBannerWrapperViewController.swift b/Riot/Modules/Home/VersionCheck/HomeViewControllerWithBannerWrapperViewController.swift index 1c6c24173..0a9fbcee8 100644 --- a/Riot/Modules/Home/VersionCheck/HomeViewControllerWithBannerWrapperViewController.swift +++ b/Riot/Modules/Home/VersionCheck/HomeViewControllerWithBannerWrapperViewController.swift @@ -16,7 +16,7 @@ import Foundation -class HomeViewControllerWithBannerWrapperViewController: UIViewController, MXKViewControllerActivityHandling, BannerPresentationProtocol { +class HomeViewControllerWithBannerWrapperViewController: UIViewController, MXKViewControllerActivityHandling, BannerPresentationProtocol, MasterTabBarItemDisplayProtocol { @objc let homeViewController: HomeViewController private var bannerContainerView: UIView! @@ -107,4 +107,10 @@ class HomeViewControllerWithBannerWrapperViewController: UIViewController, MXKVi func stopActivityIndicator() { homeViewController.stopActivityIndicator() } + + // MARK: - MasterTabBarItemDisplayProtocol + + var masterTabBarItemTitle: String { + return VectorL10n.titleHome + } } diff --git a/Riot/Modules/People/PeopleViewController.m b/Riot/Modules/People/PeopleViewController.m index 028ec8eef..4c3203774 100644 --- a/Riot/Modules/People/PeopleViewController.m +++ b/Riot/Modules/People/PeopleViewController.m @@ -26,7 +26,7 @@ #import "GeneratedInterface-Swift.h" -@interface PeopleViewController () +@interface PeopleViewController () { NSInteger directRoomsSectionNumber; RecentsDataSource *recentsDataSource; @@ -83,8 +83,6 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - [AppDelegate theDelegate].masterTabBarController.navigationItem.title = [VectorL10n titlePeople]; [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; if ([self.dataSource isKindOfClass:RecentsDataSource.class]) @@ -194,4 +192,11 @@ }]; } +#pragma mark - MasterTabBarItemDisplayProtocol + +- (NSString *)masterTabBarItemTitle +{ + return [VectorL10n titlePeople]; +} + @end diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m index 6e2adfc62..4b4afa3db 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -20,7 +20,7 @@ #import "GeneratedInterface-Swift.h" -@interface RoomsViewController () +@interface RoomsViewController () { RecentsDataSource *recentsDataSource; } @@ -66,8 +66,6 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - - [AppDelegate theDelegate].masterTabBarController.navigationItem.title = [VectorL10n titleRooms]; [AppDelegate theDelegate].masterTabBarController.tabBar.tintColor = ThemeService.shared.theme.tintColor; if ([self.dataSource isKindOfClass:RecentsDataSource.class]) @@ -165,4 +163,11 @@ } } +#pragma mark - MasterTabBarItemDisplayProtocol + +- (NSString *)masterTabBarItemTitle +{ + return [VectorL10n titleRooms]; +} + @end diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 65ed41122..1a08218b9 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -316,7 +316,7 @@ } } - titleView.titleLabel.text = self.selectedViewController.accessibilityLabel; + titleView.titleLabel.text = [self getTitleForItemViewController:self.selectedViewController]; // Need to be called in case of the controllers have been replaced [self.selectedViewController viewDidAppear:NO]; @@ -336,6 +336,8 @@ { NSInteger index = [self indexOfTabItemWithTag:tabBarIndex]; self.selectedIndex = index; + + titleView.titleLabel.text = [self getTitleForItemViewController:self.selectedViewController]; } #pragma mark - @@ -825,6 +827,17 @@ self.navigationController.navigationBar.hidden = hidden; } +- (NSString*)getTitleForItemViewController:(UIViewController*)itemViewController +{ + if ([itemViewController conformsToProtocol:@protocol(MasterTabBarItemDisplayProtocol)]) + { + UIViewController *masterTabBarItem = (UIViewController*)itemViewController; + return masterTabBarItem.masterTabBarItemTitle; + } + + return nil; +} + #pragma mark - - (void)refreshTabBarBadges @@ -1115,7 +1128,7 @@ - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController { - titleView.titleLabel.text = viewController.accessibilityLabel; + titleView.titleLabel.text = [self getTitleForItemViewController:viewController]; } @end diff --git a/Riot/Modules/TabBar/MasterTabBarItemDisplayProtocol.swift b/Riot/Modules/TabBar/MasterTabBarItemDisplayProtocol.swift new file mode 100644 index 000000000..c71fbdfe6 --- /dev/null +++ b/Riot/Modules/TabBar/MasterTabBarItemDisplayProtocol.swift @@ -0,0 +1,21 @@ +// +// 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 MasterTabBarItemDisplayProtocol { + var masterTabBarItemTitle: String { get } +} From 69f9a92a47b183b61660e6d24f75a3d2643e4e85 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Thu, 24 Feb 2022 15:46:18 +0000 Subject: [PATCH 013/139] Disable the default analytics configurations for forks. (#5688) * Disable the default analytics configurations for forks. * Check the analytics configuration instead of the PostHog Config. --- Config/BuildSettings.swift | 41 +++++++++++++------ Riot/Modules/Analytics/Analytics.swift | 4 +- .../Analytics/PHGPostHogConfiguration.swift | 9 ++-- .../AnalyticsPromptCoordinator.swift | 2 +- changelog.d/5687.misc | 1 + 5 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 changelog.d/5687.misc diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 3348ba964..cb5099346 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -156,20 +156,35 @@ final class BuildSettings: NSObject { static let roomsAllowToJoinPublicRooms: Bool = true // MARK: - Analytics - #if DEBUG - /// Host to use for PostHog analytics during development. Set to nil to disable analytics in debug builds. - static let analyticsHost: String? = "https://posthog-poc.lab.element.dev" - /// Public key for submitting analytics during development. Set to nil to disable analytics in debug builds. - static let analyticsKey: String? = "rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8" - #else - /// Host to use for PostHog analytics. Set to nil to disable analytics. - static let analyticsHost: String? = "https://posthog.hss.element.io" - /// Public key for submitting analytics. Set to nil to disable analytics. - static let analyticsKey: String? = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO" - #endif - /// The URL to open with more information about analytics terms. - static let analyticsTermsURL = URL(string: "https://element.io/cookie-policy")! + /// A type that represents how to set up the analytics module in the app. + /// + /// **Note:** Analytics are disabled by default for forks. + /// If you are maintaining a fork, set custom configurations. + struct AnalyticsConfiguration { + /// Whether or not analytics should be enabled. + let isEnabled: Bool + /// The host to use for PostHog analytics. + let host: String + /// The public key for submitting analytics. + let apiKey: String + /// The URL to open with more information about analytics terms. + let termsURL: URL + } + + #if DEBUG + /// The configuration to use for analytics during development. Set `isEnabled` to false to disable analytics in debug builds. + static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: BuildSettings.baseBundleIdentifier.starts(with: "im.vector.app"), + host: "https://posthog-poc.lab.element.dev", + apiKey: "rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8", + termsURL: URL(string: "https://element.io/cookie-policy")!) + #else + /// The configuration to use for analytics. Set `isEnabled` to false to disable analytics. + static let analyticsConfiguration = AnalyticsConfiguration(isEnabled: BuildSettings.baseBundleIdentifier.starts(with: "im.vector.app"), + host: "https://posthog.hss.element.io", + apiKey: "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", + termsURL: URL(string: "https://element.io/cookie-policy")!) + #endif // MARK: - Bug report diff --git a/Riot/Modules/Analytics/Analytics.swift b/Riot/Modules/Analytics/Analytics.swift index 48716f78e..abdeb8527 100644 --- a/Riot/Modules/Analytics/Analytics.swift +++ b/Riot/Modules/Analytics/Analytics.swift @@ -50,8 +50,8 @@ import AnalyticsEvents /// Whether to show the user the analytics opt in prompt. var shouldShowAnalyticsPrompt: Bool { - // Only show the prompt once, and when analytics are configured in BuildSettings. - !RiotSettings.shared.hasSeenAnalyticsPrompt && PHGPostHogConfiguration.standard != nil + // Only show the prompt once, and when analytics are enabled in BuildSettings. + !RiotSettings.shared.hasSeenAnalyticsPrompt && BuildSettings.analyticsConfiguration.isEnabled } /// Indicates whether the user previously accepted Matomo analytics and should be shown the upgrade prompt. diff --git a/Riot/Modules/Analytics/PHGPostHogConfiguration.swift b/Riot/Modules/Analytics/PHGPostHogConfiguration.swift index c02b85c30..8bed04920 100644 --- a/Riot/Modules/Analytics/PHGPostHogConfiguration.swift +++ b/Riot/Modules/Analytics/PHGPostHogConfiguration.swift @@ -18,11 +18,12 @@ import PostHog extension PHGPostHogConfiguration { static var standard: PHGPostHogConfiguration? { - guard let apiKey = BuildSettings.analyticsKey, let host = BuildSettings.analyticsHost else { return nil } + let analyticsConfiguration = BuildSettings.analyticsConfiguration + guard analyticsConfiguration.isEnabled else { return nil } - let configuration = PHGPostHogConfiguration(apiKey: apiKey, host: host) - configuration.shouldSendDeviceID = false + let postHogConfiguration = PHGPostHogConfiguration(apiKey: analyticsConfiguration.apiKey, host: analyticsConfiguration.host) + postHogConfiguration.shouldSendDeviceID = false - return configuration + return postHogConfiguration } } diff --git a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift index 32884fea0..f96fe3eb9 100644 --- a/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift +++ b/RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift @@ -57,7 +57,7 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable { promptType = .newUser } - let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsTermsURL) + let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsConfiguration.termsURL) let view = AnalyticsPrompt(viewModel: viewModel.context) _analyticsPromptViewModel = viewModel diff --git a/changelog.d/5687.misc b/changelog.d/5687.misc new file mode 100644 index 000000000..d0f36bb02 --- /dev/null +++ b/changelog.d/5687.misc @@ -0,0 +1 @@ +Disable the default analytics configurations for forks. \ No newline at end of file From 9afa9fd1c7800e8b6999b26a964d9749310071be Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 23 Feb 2022 16:19:11 +0000 Subject: [PATCH 014/139] Translated using Weblate (Arabic) Currently translated at 100.0% (49 of 49 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/ar/ --- Riot/Assets/ar.lproj/Localizable.strings | 55 +++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/ar.lproj/Localizable.strings b/Riot/Assets/ar.lproj/Localizable.strings index 662dc870d..455189656 100644 --- a/Riot/Assets/ar.lproj/Localizable.strings +++ b/Riot/Assets/ar.lproj/Localizable.strings @@ -19,13 +19,13 @@ "VIDEO_CONF_NAMED_FROM_USER" = "مُكالَمَة مَرئيَّة جَماعِيَّة مِن %@: %@"; /* Incoming named voice conference invite from a specific person */ -"VOICE_CONF_NAMED_FROM_USER" = "مُحادَثَة جَماعِيَّة مِن %@: '%@'"; +"VOICE_CONF_NAMED_FROM_USER" = "مُكالَمَة جَماعِيَّة مِن %@: '%@'"; /* Incoming unnamed video conference invite from a specific person */ "VIDEO_CONF_FROM_USER" = "مُكالَمَة مَرئيَّة جَماعِيَّة مِن %@"; /* Incoming unnamed voice conference invite from a specific person */ -"VOICE_CONF_FROM_USER" = "مُحادَثَة جَماعِيَّة مِن %@"; +"VOICE_CONF_FROM_USER" = "مُكالَمَة جَماعِيَّة مِن %@"; /* Incoming one-to-one video call */ "VIDEO_CALL_FROM_USER" = "مُكالَمَة مَرئيَّة مِن %@"; @@ -117,3 +117,54 @@ /* A user added a Jitsi call to a room */ "GROUP_CALL_STARTED" = "بَدَأت مُكالَمَةٌ جَماعِيَّة"; + +/* A user's membership has updated in an unknown way */ +"USER_MEMBERSHIP_UPDATED" = "%@ حَدَثَ مَلَفه الشَخْصي"; + +/* A user has change their avatar */ +"USER_UPDATED_AVATAR" = "%@ غَيْر صُّورَتِهم الرَّمزية"; + +/* A user has change their name to a new name which we don't know */ +"GENERIC_USER_UPDATED_DISPLAYNAME" = "%@ غَيْر اسْمهم"; + +/** Membership Updates **/ + +/* A user has change their name to a new name */ +"USER_UPDATED_DISPLAYNAME" = "%@ غَيْر اسْمهم إلى %@"; + +/* A user has reacted to a message, but the reaction content is unknown */ +"GENERIC_REACTION_FROM_USER" = "%@ أرسَلَ رَدّ فِعْلٍ"; + +/** Reactions **/ + +/* A user has reacted to a message, including the reaction e.g. "Alice reacted 👍". */ +"REACTION_FROM_USER" = "%@ أَبْدى رَدَّ فِعْلٍ %@"; + +/* New file message from a specific person, not referencing a room. */ +"LOCATION_FROM_USER" = "%@ شارَكَ مَوقعهم"; + +/* New file message from a specific person, not referencing a room. */ +"FILE_FROM_USER" = "%@ أرسَلَ مَلَفّ %@"; + +/* New voice message from a specific person, not referencing a room. */ +"VOICE_MESSAGE_FROM_USER" = "%@ أرسَلَ رِسالة صَوتيَّة"; + +/* New audio message from a specific person, not referencing a room. */ +"AUDIO_FROM_USER" = "%@ أرسَلَ مَلَفّ صَوتي %@"; + +/* New video message from a specific person, not referencing a room. */ +"VIDEO_FROM_USER" = "%@ أرسَلَ مَقطَع مَرئي"; + +/** Media Messages **/ + +/* New image message from a specific person, not referencing a room. */ +"PICTURE_FROM_USER" = "%@ أَرسَلَ صورة"; + +/* New message reply from a specific person in a named room. */ +"REPLY_FROM_USER_IN_ROOM_TITLE" = "%@ رَد في %@"; + +/* New message reply from a specific person, not referencing a room. */ +"REPLY_FROM_USER_TITLE" = "%@ رَد"; +/** General **/ + +"NOTIFICATION" = "إشعار"; From 9bc8cfb8e7d23fc1eba90117d0eb88381f210b5a Mon Sep 17 00:00:00 2001 From: Mohamed Date: Wed, 23 Feb 2022 15:34:59 +0000 Subject: [PATCH 015/139] Translated using Weblate (Arabic) Currently translated at 100.0% (7 of 7 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/ar/ --- Riot/Assets/ar.lproj/InfoPlist.strings | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/ar.lproj/InfoPlist.strings b/Riot/Assets/ar.lproj/InfoPlist.strings index a0d41c0b3..9e5ff9a91 100644 --- a/Riot/Assets/ar.lproj/InfoPlist.strings +++ b/Riot/Assets/ar.lproj/InfoPlist.strings @@ -1,9 +1,10 @@ -"NSContactsUsageDescription" = "لاِكتِشاف جِهات الاِتِّصال الَّتي تَستَخدِمُ Matrix بِالفِعل، يُمكِنُ لِـElement إرسَال عَناوين البَريد الإلِكتُرونيّ وأرقام الهَواتِف الَّتي في دَفتَرِ العَناوين الخاصِّ بِك إلى خادِمِ هُويَّة Matrix المُختار. يَتِّمُ تَجزِئة البَياناتِ الشَّخصيَّة قَبلَ إرسالِها حَيثُما كانَت مَدعُومَة - يُرجى مُراجَعَة سياسَة الخُصُوصيَّة الخاصَّة بِخادِم الهُويَّة لِلحُصُولِ عَلَى المَزيدِ مِنَ التَّفاصيل."; -"NSFaceIDUsageDescription" = "يُستخدَم الـFace ID لِلوُصُول إلى التَّطبيق الخاصّ بِك."; +"NSContactsUsageDescription" = "سيعَرض Element جِهات الاتصال الخاصّة بِك حَتى تتمَكَّنَ من دَعوتهم للمُحادثَة."; +"NSFaceIDUsageDescription" = "يُستخدَم Face ID لِلوُصُول إلى التَّطبيق الخاصّ بِك."; "NSCalendarsUsageDescription" = "اطَّلِع عَلَى اِجتِماعاتِك المُجَدوَلَة في التَّطبيق."; -"NSMicrophoneUsageDescription" = "المِيكرُوفُون يُستَخدَم لاِلتِقاط المَقاطِع المَرئيَّة وإجراء المُكالَمات."; +"NSMicrophoneUsageDescription" = "يحتاجَ Element الوُصول إلى المِيكرُوفُون لإجراء المُكالمات واستِقبالها، والتِقاط مقاطَع الفيدِيو، وتَسجيل الرسائل الصَوتية."; // Permissions usage explanations "NSCameraUsageDescription" = "تُستَخدَم الكاميرا لاِلتِقاط الصُّوَر، المَقاطِع المَرئيَّة وإجراءُ مُكالَمَةٍ مَرئيَّة."; "NSPhotoLibraryUsageDescription" = "مَكتَبَة الصُّوَر تُستَخدَم لإرسال الصُّوَر وَالمَقاطِع المَرئيَّة."; +"NSLocationWhenInUseUsageDescription" = "عِندَما تَشارَكَ مَوقِعك مَعَ أشَخاص، يحتاجَ Element إلى الوُصول لإظهار خَريطة لهم."; From 1946b315c241a4e1ce226c4c1c6a8b7e55913eb5 Mon Sep 17 00:00:00 2001 From: aringenbach <80891108+aringenbach@users.noreply.github.com> Date: Fri, 25 Feb 2022 10:45:47 +0100 Subject: [PATCH 016/139] Remove exclamation mark badge in invites cell (#5684) * Remove exclamation mark and improve global design in invites cell * Revert back to current design, only removing exclamation mark badge Co-authored-by: Arnaud Ringenbach --- .../People/Views/InviteRecentTableViewCell.h | 3 --- .../People/Views/InviteRecentTableViewCell.m | 6 +---- .../Views/InviteRecentTableViewCell.xib | 27 ++----------------- changelog.d/5249.change | 1 + 4 files changed, 4 insertions(+), 33 deletions(-) create mode 100644 changelog.d/5249.change diff --git a/Riot/Modules/People/Views/InviteRecentTableViewCell.h b/Riot/Modules/People/Views/InviteRecentTableViewCell.h index f3fe96891..bd45d4364 100644 --- a/Riot/Modules/People/Views/InviteRecentTableViewCell.h +++ b/Riot/Modules/People/Views/InviteRecentTableViewCell.h @@ -53,7 +53,4 @@ extern NSString *const kInviteRecentTableViewCellRoomKey; @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *leftButtonActivityIndicator; @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *rightButtonActivityIndicator; -@property (weak, nonatomic) IBOutlet UIView *noticeBadgeView; - - @end diff --git a/Riot/Modules/People/Views/InviteRecentTableViewCell.m b/Riot/Modules/People/Views/InviteRecentTableViewCell.m index d0ab6a9e3..ea71c2513 100644 --- a/Riot/Modules/People/Views/InviteRecentTableViewCell.m +++ b/Riot/Modules/People/Views/InviteRecentTableViewCell.m @@ -49,9 +49,7 @@ NSString *const kInviteRecentTableViewCellRoomKey = @"kInviteRecentTableViewCell self.rightButton.clipsToBounds = YES; [self.rightButton setTitle:[VectorL10n accept] forState:UIControlStateNormal]; [self.rightButton addTarget:self action:@selector(onRightButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - [self.noticeBadgeView.layer setCornerRadius:10]; - + self.selectionStyle = UITableViewCellSelectionStyleNone; } @@ -61,8 +59,6 @@ NSString *const kInviteRecentTableViewCellRoomKey = @"kInviteRecentTableViewCell self.leftButton.backgroundColor = ThemeService.shared.theme.tintColor; self.rightButton.backgroundColor = ThemeService.shared.theme.tintColor; - - self.noticeBadgeView.backgroundColor = ThemeService.shared.theme.noticeColor; } - (void)prepareForReuse diff --git a/Riot/Modules/People/Views/InviteRecentTableViewCell.xib b/Riot/Modules/People/Views/InviteRecentTableViewCell.xib index 45a40d9e0..5911b2ba0 100644 --- a/Riot/Modules/People/Views/InviteRecentTableViewCell.xib +++ b/Riot/Modules/People/Views/InviteRecentTableViewCell.xib @@ -1,9 +1,9 @@ - + - + @@ -89,25 +89,6 @@ - - - - - - - - - - - - - -