mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Introduce an AppMediator which will:
- combine multiple services to handle app wide requests - remove direct UIApplication access from everywhere - fix #2703 by combining UIApplication.applicationState with the WindowManager's main window appearance - leave the door open for more app control in the future (especiall for the mac)
This commit is contained in:
committed by
Stefan Ceriu
parent
bdacea10a2
commit
9a172df7fd
@@ -24,6 +24,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
private let stateMachine: AppCoordinatorStateMachine
|
||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||
private let userSessionStore: UserSessionStoreProtocol
|
||||
private let appMediator: AppMediator
|
||||
private let appSettings: AppSettings
|
||||
private let appDelegate: AppDelegate
|
||||
|
||||
@@ -65,6 +66,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
|
||||
init(appDelegate: AppDelegate) {
|
||||
windowManager = WindowManager(appDelegate: appDelegate)
|
||||
appMediator = AppMediator(windowManager: windowManager)
|
||||
|
||||
Self.setupEnvironmentVariables()
|
||||
|
||||
let appSettings = AppSettings()
|
||||
@@ -100,9 +103,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
|
||||
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
|
||||
|
||||
backgroundTaskService = UIKitBackgroundTaskService {
|
||||
UIApplication.shared
|
||||
}
|
||||
backgroundTaskService = UIKitBackgroundTaskService(appMediator: appMediator)
|
||||
|
||||
let keychainController = KeychainController(service: .sessions,
|
||||
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
|
||||
@@ -409,6 +410,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
authenticationFlowCoordinator = AuthenticationFlowCoordinator(authenticationService: authenticationService,
|
||||
bugReportService: ServiceLocator.shared.bugReportService,
|
||||
navigationRootCoordinator: navigationRootCoordinator,
|
||||
appMediator: appMediator,
|
||||
appSettings: appSettings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
@@ -471,6 +473,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
appLockService: appLockFlowCoordinator.appLockService,
|
||||
bugReportService: ServiceLocator.shared.bugReportService,
|
||||
roomTimelineControllerFactory: RoomTimelineControllerFactory(),
|
||||
appMediator: appMediator,
|
||||
appSettings: appSettings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
notificationManager: notificationManager,
|
||||
@@ -645,7 +648,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
|
||||
private func handleAppRoute(_ appRoute: AppRoute) {
|
||||
if let userSessionFlowCoordinator {
|
||||
userSessionFlowCoordinator.handleAppRoute(appRoute, animated: UIApplication.shared.applicationState == .active)
|
||||
userSessionFlowCoordinator.handleAppRoute(appRoute, animated: appMediator.appState == .active)
|
||||
} else {
|
||||
storedAppRoute = appRoute
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Copyright 2024 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 UIKit
|
||||
|
||||
class AppMediator: AppMediatorProtocol {
|
||||
private let windowManager: WindowManagerProtocol
|
||||
|
||||
// UIApplication.State won't update if we store this e.g. in the constructor
|
||||
private var application: UIApplication {
|
||||
UIApplication.shared
|
||||
}
|
||||
|
||||
init(windowManager: WindowManagerProtocol) {
|
||||
self.windowManager = windowManager
|
||||
}
|
||||
|
||||
func beginBackgroundTask(withName taskName: String?, expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier {
|
||||
application.beginBackgroundTask(withName: taskName, expirationHandler: handler)
|
||||
}
|
||||
|
||||
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {
|
||||
application.endBackgroundTask(identifier)
|
||||
}
|
||||
|
||||
func open(_ url: URL) {
|
||||
application.open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
||||
func openAppSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
|
||||
open(url)
|
||||
}
|
||||
|
||||
var backgroundTimeRemaining: TimeInterval {
|
||||
application.backgroundTimeRemaining
|
||||
}
|
||||
|
||||
@MainActor
|
||||
var appState: UIApplication.State {
|
||||
switch application.applicationState {
|
||||
case .active:
|
||||
windowManager.mainWindow.traitCollection.activeAppearance == .active ? .active : .inactive
|
||||
case .inactive:
|
||||
.inactive
|
||||
case .background:
|
||||
.background
|
||||
default:
|
||||
.inactive
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-11
@@ -18,7 +18,7 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol ApplicationProtocol {
|
||||
protocol AppMediatorProtocol {
|
||||
func beginBackgroundTask(withName taskName: String?, expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier
|
||||
|
||||
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
|
||||
@@ -29,18 +29,35 @@ protocol ApplicationProtocol {
|
||||
|
||||
var backgroundTimeRemaining: TimeInterval { get }
|
||||
|
||||
var applicationState: UIApplication.State { get }
|
||||
var appState: UIApplication.State { get }
|
||||
}
|
||||
|
||||
extension UIApplication: ApplicationProtocol {
|
||||
func open(_ url: URL) {
|
||||
open(url, options: [:], completionHandler: nil)
|
||||
}
|
||||
|
||||
func openAppSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
extension UIApplication.State: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .active:
|
||||
return "active"
|
||||
case .inactive:
|
||||
return "inactive"
|
||||
case .background:
|
||||
return "background"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIUserInterfaceActiveAppearance: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .active:
|
||||
return "active"
|
||||
case .inactive:
|
||||
return "inactive"
|
||||
case .unspecified:
|
||||
return "unspecified"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
open(url)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
@@ -44,6 +45,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
init(authenticationService: AuthenticationServiceProxyProtocol,
|
||||
bugReportService: BugReportServiceProtocol,
|
||||
navigationRootCoordinator: NavigationRootCoordinator,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
@@ -51,6 +53,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.authenticationService = authenticationService
|
||||
self.bugReportService = bugReportService
|
||||
self.navigationRootCoordinator = navigationRootCoordinator
|
||||
self.appMediator = appMediator
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.userIndicatorController = userIndicatorController
|
||||
@@ -107,7 +110,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private func startQRCodeLogin() {
|
||||
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: QRCodeLoginService(),
|
||||
orientationManager: orientationManager))
|
||||
orientationManager: orientationManager,
|
||||
appMediator: appMediator))
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else {
|
||||
return
|
||||
|
||||
@@ -46,6 +46,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let emojiProvider: EmojiProviderProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
@@ -73,6 +74,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
||||
navigationStackCoordinator: NavigationStackCoordinator,
|
||||
emojiProvider: EmojiProviderProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
@@ -83,6 +85,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
self.emojiProvider = emojiProvider
|
||||
self.appMediator = appMediator
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.userIndicatorController = userIndicatorController
|
||||
@@ -439,7 +442,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
voiceMessageMediaManager: userSession.voiceMessageMediaManager,
|
||||
emojiProvider: emojiProvider,
|
||||
completionSuggestionService: completionSuggestionService,
|
||||
appMediator: appMediator,
|
||||
appSettings: appSettings)
|
||||
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
@@ -714,7 +719,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private func presentMapNavigator(interactionMode: StaticLocationInteractionMode) {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode)
|
||||
let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode, appMediator: appMediator)
|
||||
let coordinator = StaticLocationScreenCoordinator(parameters: params)
|
||||
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
@@ -1126,6 +1131,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
||||
navigationStackCoordinator: navigationStackCoordinator,
|
||||
emojiProvider: emojiProvider,
|
||||
appMediator: appMediator,
|
||||
appSettings: appSettings,
|
||||
analytics: analytics,
|
||||
userIndicatorController: userIndicatorController,
|
||||
|
||||
@@ -30,6 +30,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationSplitCoordinator: NavigationSplitCoordinator
|
||||
private let windowManager: WindowManagerProtocol
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analytics: AnalyticsService
|
||||
private let notificationManager: NotificationManagerProtocol
|
||||
@@ -71,6 +72,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
appLockService: AppLockServiceProtocol,
|
||||
bugReportService: BugReportServiceProtocol,
|
||||
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings,
|
||||
analytics: AnalyticsService,
|
||||
notificationManager: NotificationManagerProtocol,
|
||||
@@ -81,6 +83,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.windowManager = windowManager
|
||||
self.bugReportService = bugReportService
|
||||
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
||||
self.appMediator = appMediator
|
||||
self.appSettings = appSettings
|
||||
self.analytics = analytics
|
||||
self.notificationManager = notificationManager
|
||||
@@ -439,6 +442,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
||||
navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||
emojiProvider: EmojiProvider(),
|
||||
appMediator: appMediator,
|
||||
appSettings: appSettings,
|
||||
analytics: analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
|
||||
+10
-10
@@ -16,21 +16,21 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension ApplicationMock {
|
||||
static var `default`: ApplicationProtocol {
|
||||
ApplicationMock(withState: .active,
|
||||
extension AppMediatorMock {
|
||||
static var `default`: AppMediatorProtocol {
|
||||
AppMediatorMock(withState: .active,
|
||||
backgroundTimeRemaining: 10,
|
||||
allowTasks: true)
|
||||
}
|
||||
|
||||
static var mockBroken: ApplicationProtocol {
|
||||
ApplicationMock(withState: .inactive,
|
||||
|
||||
static var mockBroken: AppMediatorProtocol {
|
||||
AppMediatorMock(withState: .inactive,
|
||||
backgroundTimeRemaining: 0,
|
||||
allowTasks: false)
|
||||
}
|
||||
|
||||
static var mockAboutToSuspend: ApplicationProtocol {
|
||||
ApplicationMock(withState: .background,
|
||||
|
||||
static var mockAboutToSuspend: AppMediatorProtocol {
|
||||
AppMediatorMock(withState: .background,
|
||||
backgroundTimeRemaining: 2,
|
||||
allowTasks: false)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ extension ApplicationMock {
|
||||
allowTasks: Bool) {
|
||||
self.init()
|
||||
|
||||
underlyingApplicationState = applicationState
|
||||
underlyingAppState = applicationState
|
||||
underlyingBackgroundTimeRemaining = backgroundTimeRemaining
|
||||
|
||||
beginBackgroundTaskWithNameExpirationHandlerClosure = { _, handler in
|
||||
@@ -747,17 +747,17 @@ class AppLockServiceMock: AppLockServiceProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
class ApplicationMock: ApplicationProtocol {
|
||||
class AppMediatorMock: AppMediatorProtocol {
|
||||
var backgroundTimeRemaining: TimeInterval {
|
||||
get { return underlyingBackgroundTimeRemaining }
|
||||
set(value) { underlyingBackgroundTimeRemaining = value }
|
||||
}
|
||||
var underlyingBackgroundTimeRemaining: TimeInterval!
|
||||
var applicationState: UIApplication.State {
|
||||
get { return underlyingApplicationState }
|
||||
set(value) { underlyingApplicationState = value }
|
||||
var appState: UIApplication.State {
|
||||
get { return underlyingAppState }
|
||||
set(value) { underlyingAppState = value }
|
||||
}
|
||||
var underlyingApplicationState: UIApplication.State!
|
||||
var underlyingAppState: UIApplication.State!
|
||||
|
||||
//MARK: - beginBackgroundTask
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import SwiftUI
|
||||
|
||||
struct StaticLocationScreenCoordinatorParameters {
|
||||
let interactionMode: StaticLocationInteractionMode
|
||||
let appMediator: AppMediatorProtocol
|
||||
}
|
||||
|
||||
enum StaticLocationScreenCoordinatorAction {
|
||||
@@ -27,7 +28,8 @@ enum StaticLocationScreenCoordinatorAction {
|
||||
}
|
||||
|
||||
final class StaticLocationScreenCoordinator: CoordinatorProtocol {
|
||||
let viewModel: StaticLocationScreenViewModelProtocol
|
||||
private let parameters: StaticLocationScreenCoordinatorParameters
|
||||
private let viewModel: StaticLocationScreenViewModelProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<StaticLocationScreenCoordinatorAction, Never> = .init()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
@@ -37,6 +39,8 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
init(parameters: StaticLocationScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = StaticLocationScreenViewModel(interactionMode: parameters.interactionMode)
|
||||
}
|
||||
|
||||
@@ -49,7 +53,7 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
|
||||
case .close:
|
||||
actionsSubject.send(.close)
|
||||
case .openSystemSettings:
|
||||
UIApplication.shared.openAppSettings()
|
||||
parameters.appMediator.openAppSettings()
|
||||
case .sendLocation(let geoURI, let isUserLocation):
|
||||
actionsSubject.send(.selectedLocation(geoURI, isUserLocation: isUserLocation))
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import SwiftUI
|
||||
struct QRCodeLoginScreenCoordinatorParameters {
|
||||
let qrCodeLoginService: QRCodeLoginServiceProtocol
|
||||
let orientationManager: OrientationManagerProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
}
|
||||
|
||||
enum QRCodeLoginScreenCoordinatorAction {
|
||||
@@ -41,7 +42,7 @@ final class QRCodeLoginScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
init(parameters: QRCodeLoginScreenCoordinatorParameters) {
|
||||
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: parameters.qrCodeLoginService,
|
||||
application: UIApplication.shared)
|
||||
appMediator: parameters.appMediator)
|
||||
orientationManager = parameters.orientationManager
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ typealias QRCodeLoginScreenViewModelType = StateStoreViewModel<QRCodeLoginScreen
|
||||
|
||||
class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScreenViewModelProtocol {
|
||||
private let qrCodeLoginService: QRCodeLoginServiceProtocol
|
||||
private let application: ApplicationProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
|
||||
private let actionsSubject: PassthroughSubject<QRCodeLoginScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<QRCodeLoginScreenViewModelAction, Never> {
|
||||
@@ -30,9 +30,9 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
}
|
||||
|
||||
init(qrCodeLoginService: QRCodeLoginServiceProtocol,
|
||||
application: ApplicationProtocol) {
|
||||
appMediator: AppMediatorProtocol) {
|
||||
self.qrCodeLoginService = qrCodeLoginService
|
||||
self.application = application
|
||||
self.appMediator = appMediator
|
||||
super.init(initialViewState: QRCodeLoginScreenViewState())
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
case .startScan:
|
||||
Task { await startScanIfPossible() }
|
||||
case .openSettings:
|
||||
application.openAppSettings()
|
||||
appMediator.openAppSettings()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class QRCodeLoginScreenViewModel: QRCodeLoginScreenViewModelType, QRCodeLoginScr
|
||||
/// Only for mocking initial states
|
||||
fileprivate init(state: QRCodeLoginState) {
|
||||
qrCodeLoginService = QRCodeLoginServiceMock(configuration: .init())
|
||||
application = ApplicationMock()
|
||||
appMediator = AppMediatorMock.default
|
||||
super.init(initialViewState: .init(state: state))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ struct RoomScreenCoordinatorParameters {
|
||||
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
let completionSuggestionService: CompletionSuggestionServiceProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
let appSettings: AppSettings
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
mediaPlayerProvider: parameters.mediaPlayerProvider,
|
||||
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
application: UIApplication.shared,
|
||||
appMediator: parameters.appMediator,
|
||||
appSettings: parameters.appSettings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: NotificationCenter.default)
|
||||
|
||||
@@ -40,7 +40,7 @@ class RoomScreenInteractionHandler {
|
||||
private let voiceMessageRecorder: VoiceMessageRecorderProtocol
|
||||
private let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let application: ApplicationProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analyticsService: AnalyticsService
|
||||
private let pollInteractionHandler: PollInteractionHandlerProtocol
|
||||
@@ -62,7 +62,7 @@ class RoomScreenInteractionHandler {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
|
||||
voiceMessageRecorder: VoiceMessageRecorderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
application: ApplicationProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings,
|
||||
analyticsService: AnalyticsService) {
|
||||
self.roomProxy = roomProxy
|
||||
@@ -72,7 +72,7 @@ class RoomScreenInteractionHandler {
|
||||
self.voiceMessageMediaManager = voiceMessageMediaManager
|
||||
self.voiceMessageRecorder = voiceMessageRecorder
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.application = application
|
||||
self.appMediator = appMediator
|
||||
self.appSettings = appSettings
|
||||
self.analyticsService = analyticsService
|
||||
pollInteractionHandler = PollInteractionHandler(analyticsService: analyticsService, roomProxy: roomProxy)
|
||||
@@ -623,7 +623,7 @@ class RoomScreenInteractionHandler {
|
||||
}
|
||||
|
||||
private func openSystemSettings() {
|
||||
application.openAppSettings()
|
||||
appMediator.openAppSettings()
|
||||
}
|
||||
|
||||
private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction {
|
||||
|
||||
@@ -32,7 +32,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
private let timelineController: RoomTimelineControllerProtocol
|
||||
private let mediaPlayerProvider: MediaPlayerProviderProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let application: ApplicationProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analyticsService: AnalyticsService
|
||||
private let notificationCenter: NotificationCenterProtocol
|
||||
@@ -54,7 +54,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
mediaPlayerProvider: MediaPlayerProviderProtocol,
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
application: ApplicationProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings,
|
||||
analyticsService: AnalyticsService,
|
||||
notificationCenter: NotificationCenterProtocol) {
|
||||
@@ -64,7 +64,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
self.appSettings = appSettings
|
||||
self.analyticsService = analyticsService
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.application = application
|
||||
self.appMediator = appMediator
|
||||
self.notificationCenter = notificationCenter
|
||||
|
||||
let voiceMessageRecorder = VoiceMessageRecorder(audioRecorder: AudioRecorder(), mediaPlayerProvider: mediaPlayerProvider)
|
||||
@@ -76,7 +76,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
voiceMessageMediaManager: voiceMessageMediaManager,
|
||||
voiceMessageRecorder: voiceMessageRecorder,
|
||||
userIndicatorController: userIndicatorController,
|
||||
application: application,
|
||||
appMediator: appMediator,
|
||||
appSettings: appSettings,
|
||||
analyticsService: analyticsService)
|
||||
|
||||
@@ -417,7 +417,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
|
||||
private func sendReadReceiptIfNeeded(for lastVisibleItemID: TimelineItemIdentifier) async {
|
||||
guard application.applicationState == .active else { return }
|
||||
guard appMediator.appState == .active else { return }
|
||||
|
||||
await timelineController.sendReadReceipt(for: lastVisibleItemID)
|
||||
}
|
||||
@@ -659,7 +659,7 @@ extension RoomScreenViewModel {
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
application: ApplicationMock.default,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: NotificationCenterMock())
|
||||
|
||||
@@ -58,7 +58,7 @@ struct ReadReceiptsSummaryView_Previews: PreviewProvider, TestablePreview {
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
application: ApplicationMock(),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: NotificationCenterMock())
|
||||
|
||||
@@ -192,7 +192,7 @@ struct RoomScreen_Previews: PreviewProvider, TestablePreview {
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
application: ApplicationMock.default,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: NotificationCenterMock())
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
application: ApplicationMock.default,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: NotificationCenterMock())
|
||||
|
||||
@@ -83,7 +83,7 @@ struct UITimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
mediaPlayerProvider: MediaPlayerProviderMock(),
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
application: ApplicationMock.default,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: NotificationCenterMock())
|
||||
|
||||
+5
-15
@@ -17,7 +17,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// UIKitBackgroundTask is a concrete implementation of BackgroundTaskProtocol using UIApplication background task.
|
||||
class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
let name: String
|
||||
var isRunning: Bool {
|
||||
@@ -30,28 +29,21 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
Date().timeIntervalSince(startDate) * 1000
|
||||
}
|
||||
|
||||
private let application: ApplicationProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private var identifier: UIBackgroundTaskIdentifier = .invalid
|
||||
private var useCounter = 0
|
||||
private let startDate = Date()
|
||||
|
||||
/// Initializes and starts a new background task
|
||||
/// - Parameters:
|
||||
/// - name: name
|
||||
/// - isReusable: flag indicating the task is reusable
|
||||
/// - application: application instance
|
||||
/// - expirationHandler: expiration handler
|
||||
init?(name: String,
|
||||
isReusable: Bool,
|
||||
application: ApplicationProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
expirationHandler: BackgroundTaskExpirationHandler?) {
|
||||
self.name = name
|
||||
self.isReusable = isReusable
|
||||
self.application = application
|
||||
self.appMediator = appMediator
|
||||
self.expirationHandler = expirationHandler
|
||||
|
||||
// attempt to start
|
||||
identifier = application.beginBackgroundTask(withName: name) { [weak self] in
|
||||
identifier = appMediator.beginBackgroundTask(withName: name) { [weak self] in
|
||||
guard let self else { return }
|
||||
self.stop()
|
||||
self.expirationHandler?(self)
|
||||
@@ -59,13 +51,11 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
|
||||
if identifier == .invalid {
|
||||
MXLog.error("Do not start background task: \(name), as OS declined")
|
||||
// call expiration handler immediately
|
||||
expirationHandler?(self)
|
||||
return nil
|
||||
}
|
||||
|
||||
if isReusable {
|
||||
// creation itself is a use
|
||||
reuse()
|
||||
}
|
||||
|
||||
@@ -94,7 +84,7 @@ class UIKitBackgroundTask: BackgroundTaskProtocol {
|
||||
if identifier != .invalid {
|
||||
MXLog.verbose("End background task #\(identifier.rawValue) - \(name) after \(readableElapsedTime)")
|
||||
|
||||
application.endBackgroundTask(identifier)
|
||||
appMediator.endBackgroundTask(identifier)
|
||||
identifier = .invalid
|
||||
}
|
||||
}
|
||||
+12
-47
@@ -19,28 +19,17 @@ import UIKit
|
||||
|
||||
/// /// UIKitBackgroundTaskService is a concrete implementation of BackgroundTaskServiceProtocol using a given `ApplicationProtocol` instance.
|
||||
class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
private let applicationBlock: () -> ApplicationProtocol?
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private var reusableTasks = NSMapTable<NSString, UIKitBackgroundTask>(keyOptions: .strongMemory, valueOptions: .weakMemory)
|
||||
|
||||
private var application: ApplicationProtocol? {
|
||||
applicationBlock()
|
||||
}
|
||||
|
||||
/// Initializer
|
||||
/// - Parameter applicationBlock: block returning the application instance to use. Defaults to a block returning `UIApplication.extensionSafeShared`.
|
||||
init(withApplicationBlock applicationBlock: @escaping () -> ApplicationProtocol? = { UIApplication.extensionSafeShared }) {
|
||||
self.applicationBlock = applicationBlock
|
||||
|
||||
init(appMediator: AppMediatorProtocol) {
|
||||
self.appMediator = appMediator
|
||||
}
|
||||
|
||||
func startBackgroundTask(withName name: String,
|
||||
isReusable: Bool,
|
||||
expirationHandler: (() -> Void)?) -> BackgroundTaskProtocol? {
|
||||
guard let application else {
|
||||
MXLog.error("Do not start background task: \(name). Application is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
if avoidStartingNewTasks(for: application) {
|
||||
if shouldAvoidStartingNewTasks() {
|
||||
MXLog.error("Do not start background task: \(name), as not enough time exists")
|
||||
// call expiration handler immediately
|
||||
expirationHandler?()
|
||||
@@ -56,7 +45,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
} else {
|
||||
if let newTask = UIKitBackgroundTask(name: name,
|
||||
isReusable: isReusable,
|
||||
application: application,
|
||||
appMediator: appMediator,
|
||||
expirationHandler: { [weak self] task in
|
||||
guard let self else { return }
|
||||
self.reusableTasks.removeObject(forKey: task.name as NSString)
|
||||
@@ -70,7 +59,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
} else {
|
||||
if let newTask = UIKitBackgroundTask(name: name,
|
||||
isReusable: isReusable,
|
||||
application: application,
|
||||
appMediator: appMediator,
|
||||
expirationHandler: { _ in
|
||||
expirationHandler?()
|
||||
}) {
|
||||
@@ -79,8 +68,8 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
let appState = application.applicationState
|
||||
let remainingTime = readableBackgroundTimeRemaining(application.backgroundTimeRemaining)
|
||||
let appState = appMediator.appState
|
||||
let remainingTime = readableBackgroundTimeRemaining(appMediator.backgroundTimeRemaining)
|
||||
|
||||
MXLog.verbose("Background task \(name) \(created ? "started" : "reused") with app state: \(appState) and estimated background time remaining: \(remainingTime)")
|
||||
|
||||
@@ -95,9 +84,9 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func avoidStartingNewTasks(for application: ApplicationProtocol) -> Bool {
|
||||
if application.applicationState == .background,
|
||||
application.backgroundTimeRemaining < .backgroundTimeRemainingThresholdToStartTasks {
|
||||
private func shouldAvoidStartingNewTasks() -> Bool {
|
||||
if appMediator.appState == .background,
|
||||
appMediator.backgroundTimeRemaining < .backgroundTimeRemainingThresholdToStartTasks {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -107,27 +96,3 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
|
||||
private extension TimeInterval {
|
||||
static let backgroundTimeRemainingThresholdToStartTasks: TimeInterval = 5
|
||||
}
|
||||
|
||||
private extension UIApplication {
|
||||
/// Application instance extension-safe. Will be `nil` on app extensions.
|
||||
static var extensionSafeShared: UIApplication? {
|
||||
let selector = NSSelectorFromString("sharedApplication")
|
||||
guard responds(to: selector) else { return nil }
|
||||
return perform(selector).takeUnretainedValue() as? UIApplication
|
||||
}
|
||||
}
|
||||
|
||||
extension UIApplication.State: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .active:
|
||||
return "active"
|
||||
case .inactive:
|
||||
return "inactive"
|
||||
case .background:
|
||||
return "background"
|
||||
@unknown default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@ class MockScreen: Identifiable {
|
||||
let flowCoordinator = AuthenticationFlowCoordinator(authenticationService: MockAuthenticationServiceProxy(),
|
||||
bugReportService: BugReportServiceMock(),
|
||||
navigationRootCoordinator: navigationRootCoordinator,
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
@@ -243,6 +244,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
@@ -258,6 +260,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
@@ -273,6 +276,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
@@ -288,6 +292,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
@@ -306,6 +311,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -324,6 +330,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -342,6 +349,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -361,6 +369,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -379,6 +388,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -397,6 +407,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -415,6 +426,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -433,6 +445,7 @@ class MockScreen: Identifiable {
|
||||
voiceMessageMediaManager: VoiceMessageMediaManagerMock(),
|
||||
emojiProvider: EmojiProvider(),
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -460,6 +473,7 @@ class MockScreen: Identifiable {
|
||||
appSettings: ServiceLocator.shared.settings),
|
||||
bugReportService: BugReportServiceMock(),
|
||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: appSettings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
notificationManager: NotificationManagerMock(),
|
||||
|
||||
Reference in New Issue
Block a user