mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Use the SDK's offline detection everywhere (except for restarting the sync loop). (#4472)
* Use the SDK's offline state to drive the offline indicator. * Only use network reachability for restarting the sync loop, use the homeserver reachability for requests. * Add a separate indicator to distinguish when the server is unreachable but the device is online.
This commit is contained in:
@@ -278,6 +278,7 @@
|
||||
"common_sent" = "Sent";
|
||||
"common_sentence_delimiter" = ". ";
|
||||
"common_server_not_supported" = "Server not supported";
|
||||
"common_server_unreachable" = "Server unreachable";
|
||||
"common_server_url" = "Server URL";
|
||||
"common_settings" = "Settings";
|
||||
"common_shared_location" = "Shared location";
|
||||
|
||||
@@ -150,7 +150,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
setupStateMachine()
|
||||
|
||||
observeApplicationState()
|
||||
observeNetworkState()
|
||||
observeAppLockChanges()
|
||||
|
||||
registerBackgroundAppRefresh()
|
||||
@@ -826,24 +825,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
}
|
||||
}
|
||||
|
||||
private func observeNetworkState() {
|
||||
let reachabilityNotificationIdentifier = "io.element.elementx.reachability.notification"
|
||||
appMediator.networkMonitor
|
||||
.reachabilityPublisher
|
||||
.sink { reachability in
|
||||
MXLog.info("Reachability changed to \(reachability)")
|
||||
|
||||
if reachability == .reachable {
|
||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(reachabilityNotificationIdentifier)
|
||||
} else {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: reachabilityNotificationIdentifier,
|
||||
title: L10n.commonOffline,
|
||||
persistent: true))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func observeAppLockChanges() {
|
||||
appLockFlowCoordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
@@ -1046,7 +1027,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
|
||||
switch state {
|
||||
case .loading:
|
||||
if self?.appMediator.networkMonitor.reachabilityPublisher.value == .reachable {
|
||||
if self?.userSession?.clientProxy.homeserverReachabilityPublisher.value == .reachable {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: toastIdentifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true))
|
||||
}
|
||||
case .notLoading:
|
||||
|
||||
@@ -769,7 +769,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
userIndicatorController: flowParameters.userIndicatorController,
|
||||
notificationSettings: userSession.clientProxy.notificationSettings,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: flowParameters.appMediator,
|
||||
appSettings: flowParameters.appSettings)
|
||||
let coordinator = RoomDetailsScreenCoordinator(parameters: params)
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
|
||||
@@ -239,6 +239,29 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
let reachabilityNotificationID = "io.element.elementx.reachability.notification"
|
||||
userSession.clientProxy.homeserverReachabilityPublisher.removeDuplicates()
|
||||
.combineLatest(flowParameters.appMediator.networkMonitor.reachabilityPublisher.removeDuplicates())
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] homeserverReachability, networkReachability in
|
||||
MXLog.info("Homeserver reachability: \(homeserverReachability)")
|
||||
|
||||
guard let self else { return }
|
||||
switch (homeserverReachability, networkReachability) {
|
||||
case (.reachable, _):
|
||||
flowParameters.userIndicatorController.retractIndicatorWithId(reachabilityNotificationID)
|
||||
case (.unreachable, .unreachable):
|
||||
flowParameters.userIndicatorController.submitIndicator(.init(id: reachabilityNotificationID,
|
||||
title: L10n.commonOffline,
|
||||
persistent: true))
|
||||
case (.unreachable, .reachable):
|
||||
flowParameters.userIndicatorController.submitIndicator(.init(id: reachabilityNotificationID,
|
||||
title: L10n.commonServerUnreachable,
|
||||
persistent: true))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
onboardingFlowCoordinator.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
@@ -507,7 +530,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private func presentSecureBackupLogoutConfirmationScreen() {
|
||||
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
|
||||
appMediator: flowParameters.appMediator))
|
||||
homeserverReachabilityPublisher: userSession.clientProxy.homeserverReachabilityPublisher))
|
||||
|
||||
coordinator.actions
|
||||
.sink { [weak self] action in
|
||||
|
||||
@@ -630,6 +630,8 @@ internal enum L10n {
|
||||
internal static var commonSentenceDelimiter: String { return L10n.tr("Localizable", "common_sentence_delimiter") }
|
||||
/// Server not supported
|
||||
internal static var commonServerNotSupported: String { return L10n.tr("Localizable", "common_server_not_supported") }
|
||||
/// Server unreachable
|
||||
internal static var commonServerUnreachable: String { return L10n.tr("Localizable", "common_server_unreachable") }
|
||||
/// Server URL
|
||||
internal static var commonServerUrl: String { return L10n.tr("Localizable", "common_server_url") }
|
||||
/// Settings
|
||||
|
||||
@@ -48,14 +48,14 @@ extension ClientProxyMock {
|
||||
roomDirectorySearchProxyReturnValue = configuration.roomDirectorySearchProxy
|
||||
|
||||
actionsPublisher = PassthroughSubject<ClientProxyAction, Never>().eraseToAnyPublisher()
|
||||
loadingStatePublisher = CurrentValuePublisher<ClientProxyLoadingState, Never>(.notLoading)
|
||||
verificationStatePublisher = CurrentValuePublisher<SessionVerificationState, Never>(.unknown)
|
||||
loadingStatePublisher = .init(.notLoading)
|
||||
verificationStatePublisher = .init(.unknown)
|
||||
homeserverReachabilityPublisher = .init(.reachable)
|
||||
|
||||
userAvatarURLPublisher = CurrentValueSubject<URL?, Never>(nil).asCurrentValuePublisher()
|
||||
userAvatarURLPublisher = .init(nil)
|
||||
userDisplayNamePublisher = .init("User display name")
|
||||
|
||||
userDisplayNamePublisher = CurrentValueSubject<String?, Never>("User display name").asCurrentValuePublisher()
|
||||
|
||||
ignoredUsersPublisher = CurrentValueSubject<[String]?, Never>([RoomMemberProxyMock].allMembers.map(\.userID)).asCurrentValuePublisher()
|
||||
ignoredUsersPublisher = .init([RoomMemberProxyMock].allMembers.map(\.userID))
|
||||
|
||||
notificationSettings = configuration.notificationSettings
|
||||
|
||||
|
||||
@@ -2082,6 +2082,11 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
set(value) { underlyingVerificationStatePublisher = value }
|
||||
}
|
||||
var underlyingVerificationStatePublisher: CurrentValuePublisher<SessionVerificationState, Never>!
|
||||
var homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never> {
|
||||
get { return underlyingHomeserverReachabilityPublisher }
|
||||
set(value) { underlyingHomeserverReachabilityPublisher = value }
|
||||
}
|
||||
var underlyingHomeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>!
|
||||
var userID: String {
|
||||
get { return underlyingUserID }
|
||||
set(value) { underlyingUserID = value }
|
||||
|
||||
@@ -26,8 +26,10 @@ class NetworkMonitor: NetworkMonitorProtocol {
|
||||
pathMonitor.pathUpdateHandler = { [weak self] path in
|
||||
DispatchQueue.main.async {
|
||||
if path.status == .satisfied {
|
||||
MXLog.info("Network reachability changed to reachable")
|
||||
self?.reachabilitySubject.send(.reachable)
|
||||
} else {
|
||||
MXLog.info("Network reachability changed to unreachable")
|
||||
self?.reachabilitySubject.send(.unreachable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ struct RoomDetailsScreenCoordinatorParameters {
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
let notificationSettings: NotificationSettingsProxyProtocol
|
||||
let attributedStringBuilder: AttributedStringBuilderProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
let appSettings: AppSettings
|
||||
}
|
||||
|
||||
@@ -54,7 +53,6 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
||||
userIndicatorController: parameters.userIndicatorController,
|
||||
notificationSettingsProxy: parameters.notificationSettings,
|
||||
attributedStringBuilder: parameters.attributedStringBuilder,
|
||||
appMediator: parameters.appMediator,
|
||||
appSettings: parameters.appSettings)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
notificationSettingsProxy: NotificationSettingsProxyProtocol,
|
||||
attributedStringBuilder: AttributedStringBuilderProtocol,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings) {
|
||||
self.roomProxy = roomProxy
|
||||
self.userSession = userSession
|
||||
@@ -79,7 +78,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
state.reportRoomEnabled = await userSession.clientProxy.isReportRoomSupported
|
||||
}
|
||||
|
||||
appMediator.networkMonitor.reachabilityPublisher
|
||||
userSession.clientProxy.homeserverReachabilityPublisher
|
||||
.filter { $0 == .reachable }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
|
||||
@@ -397,7 +397,6 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxy,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
|
||||
@@ -427,7 +426,6 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxy,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
|
||||
@@ -466,7 +464,6 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxy,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
roomProxy: parameters.roomProxy,
|
||||
initialSelectedPinnedEventID: selectedPinnedEventID,
|
||||
ongoingCallRoomIDPublisher: parameters.ongoingCallRoomIDPublisher,
|
||||
appMediator: parameters.appMediator,
|
||||
appSettings: parameters.appSettings,
|
||||
appHooks: parameters.appHooks,
|
||||
analyticsService: parameters.analytics,
|
||||
|
||||
@@ -16,7 +16,6 @@ typealias RoomScreenViewModelType = StateStoreViewModel<RoomScreenViewState, Roo
|
||||
class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol {
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let analyticsService: AnalyticsService
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
@@ -54,14 +53,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
roomProxy: JoinedRoomProxyProtocol,
|
||||
initialSelectedPinnedEventID: String?,
|
||||
ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>,
|
||||
appMediator: AppMediatorProtocol,
|
||||
appSettings: AppSettings,
|
||||
appHooks: AppHooks,
|
||||
analyticsService: AnalyticsService,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
clientProxy = userSession.clientProxy
|
||||
self.roomProxy = roomProxy
|
||||
self.appMediator = appMediator
|
||||
self.appSettings = appSettings
|
||||
self.analyticsService = analyticsService
|
||||
self.userIndicatorController = userIndicatorController
|
||||
@@ -179,7 +176,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
appMediator.networkMonitor.reachabilityPublisher
|
||||
clientProxy.homeserverReachabilityPublisher
|
||||
.filter { $0 == .reachable }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
@@ -413,13 +410,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
extension RoomScreenViewModel {
|
||||
static func mock(roomProxyMock: JoinedRoomProxyMock,
|
||||
clientProxyMock: ClientProxyMock = ClientProxyMock(),
|
||||
clientProxyMock: ClientProxyMock = ClientProxyMock(.init()),
|
||||
appHooks: AppHooks = AppHooks()) -> RoomScreenViewModel {
|
||||
RoomScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxyMock)),
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: appHooks,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
|
||||
struct SecureBackupLogoutConfirmationScreenCoordinatorParameters {
|
||||
let secureBackupController: SecureBackupControllerProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
let homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>
|
||||
}
|
||||
|
||||
enum SecureBackupLogoutConfirmationScreenCoordinatorAction {
|
||||
@@ -30,7 +30,7 @@ final class SecureBackupLogoutConfirmationScreenCoordinator: CoordinatorProtocol
|
||||
|
||||
init(parameters: SecureBackupLogoutConfirmationScreenCoordinatorParameters) {
|
||||
viewModel = SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: parameters.secureBackupController,
|
||||
appMediator: parameters.appMediator)
|
||||
homeserverReachabilityPublisher: parameters.homeserverReachabilityPublisher)
|
||||
}
|
||||
|
||||
func start() {
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@ typealias SecureBackupLogoutConfirmationScreenViewModelType = StateStoreViewMode
|
||||
|
||||
class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmationScreenViewModelType, SecureBackupLogoutConfirmationScreenViewModelProtocol {
|
||||
private let secureBackupController: SecureBackupControllerProtocol
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>
|
||||
|
||||
private let backupUploadStateSubject: CurrentValueSubject<SecureBackupSteadyState, Never> = .init(.waiting)
|
||||
|
||||
@@ -27,13 +27,13 @@ class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmat
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(secureBackupController: SecureBackupControllerProtocol, appMediator: AppMediatorProtocol) {
|
||||
init(secureBackupController: SecureBackupControllerProtocol, homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>) {
|
||||
self.secureBackupController = secureBackupController
|
||||
self.appMediator = appMediator
|
||||
self.homeserverReachabilityPublisher = homeserverReachabilityPublisher
|
||||
|
||||
super.init(initialViewState: .init(mode: .saveRecoveryKey))
|
||||
|
||||
backupUploadStateSubject.combineLatest(appMediator.networkMonitor.reachabilityPublisher)
|
||||
backupUploadStateSubject.combineLatest(homeserverReachabilityPublisher)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] backupState, reachability in
|
||||
guard let self, state.mode != .saveRecoveryKey else { return }
|
||||
@@ -62,7 +62,7 @@ class SecureBackupLogoutConfirmationScreenViewModel: SecureBackupLogoutConfirmat
|
||||
|
||||
private func attemptLogout() {
|
||||
if case .saveRecoveryKey = state.mode {
|
||||
updateMode(backupState: backupUploadStateSubject.value, reachability: appMediator.networkMonitor.reachabilityPublisher.value)
|
||||
updateMode(backupState: backupUploadStateSubject.value, reachability: homeserverReachabilityPublisher.value)
|
||||
|
||||
keyUploadWaitingTask = Task {
|
||||
var result = await secureBackupController.waitForKeyBackupUpload(uploadStateSubject: backupUploadStateSubject)
|
||||
|
||||
+1
-6
@@ -161,14 +161,9 @@ struct SecureBackupLogoutConfirmationScreen_Previews: PreviewProvider, TestableP
|
||||
}
|
||||
|
||||
let reachability: NetworkMonitorReachability = mode == .offline ? .unreachable : .reachable
|
||||
let networkMonitor = NetworkMonitorMock()
|
||||
networkMonitor.underlyingReachabilityPublisher = CurrentValueSubject<NetworkMonitorReachability, Never>(reachability).asCurrentValuePublisher()
|
||||
|
||||
let appMediator = AppMediatorMock()
|
||||
appMediator.underlyingNetworkMonitor = networkMonitor
|
||||
|
||||
let viewModel = SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: secureBackupController,
|
||||
appMediator: appMediator)
|
||||
homeserverReachabilityPublisher: .init(reachability))
|
||||
|
||||
if mode != .saveRecoveryKey {
|
||||
viewModel.context.send(viewAction: .logout)
|
||||
|
||||
@@ -137,6 +137,11 @@ class ClientProxy: ClientProxyProtocol {
|
||||
verificationStateSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private let homeserverReachabilitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
|
||||
var homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never> {
|
||||
homeserverReachabilitySubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private let timelineMediaVisibilitySubject = CurrentValueSubject<TimelineMediaVisibility, Never>(.always)
|
||||
var timelineMediaVisibilityPublisher: CurrentValuePublisher<TimelineMediaVisibility, Never> {
|
||||
timelineMediaVisibilitySubject.asCurrentValuePublisher()
|
||||
@@ -218,10 +223,10 @@ class ClientProxy: ClientProxyProtocol {
|
||||
})
|
||||
|
||||
sendQueueStatusSubject
|
||||
.combineLatest(networkMonitor.reachabilityPublisher)
|
||||
.combineLatest(homeserverReachabilityPublisher)
|
||||
.debounce(for: 1.0, scheduler: DispatchQueue.main)
|
||||
.sink { enabled, reachability in
|
||||
MXLog.info("Send queue status changed to enabled: \(enabled), reachability: \(reachability)")
|
||||
MXLog.info("Send queue status changed to enabled: \(enabled), homeserver reachability: \(reachability)")
|
||||
|
||||
if enabled == false, reachability == .reachable {
|
||||
MXLog.info("Enabling all send queues")
|
||||
@@ -926,12 +931,11 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
switch state {
|
||||
case .running, .terminated, .idle:
|
||||
break
|
||||
homeserverReachabilitySubject.send(.reachable)
|
||||
case .offline:
|
||||
homeserverReachabilitySubject.send(.unreachable)
|
||||
case .error:
|
||||
restartSync()
|
||||
case .offline:
|
||||
// This needs to be enabled in the client builder first to be actually used
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1196,6 +1200,7 @@ private struct ClientProxyServices {
|
||||
let syncService = try await client
|
||||
.syncService()
|
||||
.withCrossProcessLock()
|
||||
.withOfflineMode()
|
||||
.withSharePos(enable: true)
|
||||
.finish()
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
var verificationStatePublisher: CurrentValuePublisher<SessionVerificationState, Never> { get }
|
||||
|
||||
var homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never> { get }
|
||||
|
||||
var userID: String { get }
|
||||
|
||||
var deviceID: String? { get }
|
||||
|
||||
@@ -12,14 +12,14 @@ import UIKit
|
||||
class MediaProvider: MediaProviderProtocol {
|
||||
private let mediaLoader: MediaLoaderProtocol
|
||||
private let imageCache: Kingfisher.ImageCache
|
||||
private let networkMonitor: NetworkMonitorProtocol?
|
||||
private let homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>?
|
||||
|
||||
init(mediaLoader: MediaLoaderProtocol,
|
||||
imageCache: Kingfisher.ImageCache,
|
||||
networkMonitor: NetworkMonitorProtocol?) {
|
||||
homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never>?) {
|
||||
self.mediaLoader = mediaLoader
|
||||
self.imageCache = imageCache
|
||||
self.networkMonitor = networkMonitor
|
||||
self.homeserverReachabilityPublisher = homeserverReachabilityPublisher
|
||||
}
|
||||
|
||||
// MARK: Images
|
||||
@@ -67,8 +67,8 @@ class MediaProvider: MediaProviderProtocol {
|
||||
}
|
||||
|
||||
func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task<UIImage, any Error> {
|
||||
guard let networkMonitor else {
|
||||
fatalError("This method shouldn't be invoked without a NetworkMonitor set.")
|
||||
guard let homeserverReachabilityPublisher else {
|
||||
fatalError("This method shouldn't be invoked without a homeserver reachability publisher set.")
|
||||
}
|
||||
|
||||
return Task {
|
||||
@@ -80,7 +80,7 @@ class MediaProvider: MediaProviderProtocol {
|
||||
throw MediaProviderError.cancelled
|
||||
}
|
||||
|
||||
for await reachability in networkMonitor.reachabilityPublisher.values {
|
||||
for await reachability in homeserverReachabilityPublisher.values {
|
||||
guard !Task.isCancelled else {
|
||||
throw MediaProviderError.cancelled
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol {
|
||||
let mediaProvider = MediaProvider(mediaLoader: clientProxy,
|
||||
imageCache: .onlyInMemory,
|
||||
networkMonitor: networkMonitor)
|
||||
homeserverReachabilityPublisher: clientProxy.homeserverReachabilityPublisher)
|
||||
|
||||
let voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ final class NSEUserSession {
|
||||
private let userID: String
|
||||
private(set) lazy var mediaProvider: MediaProviderProtocol = MediaProvider(mediaLoader: MediaLoader(client: baseClient),
|
||||
imageCache: .onlyOnDisk,
|
||||
networkMonitor: nil)
|
||||
homeserverReachabilityPublisher: nil)
|
||||
private let delegateHandle: TaskHandle?
|
||||
|
||||
var mediaPreviewVisibility: MediaPreviews {
|
||||
|
||||
@@ -15,18 +15,17 @@ import XCTest
|
||||
final class MediaProviderTests: XCTestCase {
|
||||
private var mediaLoader: MediaLoaderMock!
|
||||
private var imageCache: MockImageCache!
|
||||
private var networkMonitor: NetworkMonitorMock!
|
||||
private var reachabilitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
|
||||
|
||||
var mediaProvider: MediaProvider!
|
||||
|
||||
override func setUp() {
|
||||
mediaLoader = MediaLoaderMock()
|
||||
imageCache = MockImageCache(name: "Test")
|
||||
networkMonitor = NetworkMonitorMock()
|
||||
|
||||
mediaProvider = MediaProvider(mediaLoader: mediaLoader,
|
||||
imageCache: imageCache,
|
||||
networkMonitor: networkMonitor)
|
||||
homeserverReachabilityPublisher: reachabilitySubject.asCurrentValuePublisher())
|
||||
}
|
||||
|
||||
func testLoadingRetriedOnReconnection() async throws {
|
||||
@@ -38,20 +37,18 @@ final class MediaProviderTests: XCTestCase {
|
||||
|
||||
let loadTask = try mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"))
|
||||
|
||||
let connectivitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.unreachable)
|
||||
reachabilitySubject.send(.unreachable)
|
||||
|
||||
mediaLoader.loadMediaContentForSourceClosure = { _ in
|
||||
switch connectivitySubject.value {
|
||||
mediaLoader.loadMediaContentForSourceClosure = { [reachabilitySubject] _ in
|
||||
switch reachabilitySubject.value {
|
||||
case .unreachable:
|
||||
connectivitySubject.send(.reachable)
|
||||
reachabilitySubject.send(.reachable)
|
||||
throw MediaProviderTestsError.error
|
||||
case .reachable:
|
||||
return pngData
|
||||
}
|
||||
}
|
||||
|
||||
networkMonitor.underlyingReachabilityPublisher = connectivitySubject.asCurrentValuePublisher()
|
||||
|
||||
let result = try? await loadTask.value
|
||||
|
||||
XCTAssertNotNil(result)
|
||||
@@ -61,12 +58,10 @@ final class MediaProviderTests: XCTestCase {
|
||||
func testLoadingRetriedOnReconnectionCancelsAfterSecondFailure() async throws {
|
||||
let loadTask = try mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: .mockMXCImage, mimeType: "image/jpeg"))
|
||||
|
||||
let connectivitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
|
||||
reachabilitySubject.send(.reachable)
|
||||
|
||||
mediaLoader.loadMediaContentForSourceThrowableError = MediaProviderTestsError.error
|
||||
|
||||
networkMonitor.underlyingReachabilityPublisher = connectivitySubject.asCurrentValuePublisher()
|
||||
|
||||
let result = try? await loadTask.value
|
||||
|
||||
XCTAssertNil(result)
|
||||
|
||||
@@ -32,7 +32,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
}
|
||||
|
||||
@@ -45,7 +44,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let deferred = deferFulfillment(context.observe(\.viewState.bindings.leaveRoomAlertItem)) { $0 != nil }
|
||||
|
||||
@@ -65,7 +63,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
let deferred = deferFulfillment(context.observe(\.viewState.bindings.leaveRoomAlertItem)) { $0 != nil }
|
||||
|
||||
@@ -86,7 +83,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
context.send(viewAction: .processTapLeave)
|
||||
@@ -139,7 +135,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
|
||||
@@ -160,7 +155,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
|
||||
@@ -191,7 +185,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
|
||||
@@ -221,7 +214,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
|
||||
@@ -252,7 +244,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferredRecipient = deferFulfillment(viewModel.context.observe(\.viewState.dmRecipientInfo)) { $0 != nil }
|
||||
@@ -284,7 +275,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -301,7 +291,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -353,7 +342,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -392,7 +380,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -431,7 +418,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -451,7 +437,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -471,7 +456,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
_ = await context.observe(\.viewState).debounce(for: .milliseconds(100)).first()
|
||||
@@ -489,7 +473,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
var deferred = deferFulfillment(context.observe(\.viewState.notificationSettingsState)) { $0.isError }
|
||||
@@ -682,7 +665,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
|
||||
@@ -704,7 +686,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
|
||||
@@ -728,7 +709,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
|
||||
@@ -752,7 +732,6 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
let deferred = deferFulfillment(context.observe(\.viewState)) { state in
|
||||
|
||||
@@ -42,7 +42,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -122,7 +121,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: "test1",
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -180,7 +178,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -217,7 +214,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: ongoingCallRoomIDSubject.asCurrentValuePublisher(),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -262,7 +258,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -284,7 +279,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -319,7 +313,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -349,7 +342,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
@@ -370,7 +362,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
|
||||
@@ -23,14 +23,9 @@ class SecureBackupLogoutConfirmationScreenViewModelTests: XCTestCase {
|
||||
secureBackupController.underlyingKeyBackupState = CurrentValueSubject<SecureBackupKeyBackupState, Never>(.enabled).asCurrentValuePublisher()
|
||||
|
||||
reachabilitySubject = CurrentValueSubject<NetworkMonitorReachability, Never>(.reachable)
|
||||
let networkMonitor = NetworkMonitorMock()
|
||||
networkMonitor.underlyingReachabilityPublisher = reachabilitySubject.asCurrentValuePublisher()
|
||||
|
||||
let appMediator = AppMediatorMock()
|
||||
appMediator.underlyingNetworkMonitor = networkMonitor
|
||||
|
||||
viewModel = SecureBackupLogoutConfirmationScreenViewModel(secureBackupController: secureBackupController,
|
||||
appMediator: appMediator)
|
||||
homeserverReachabilityPublisher: reachabilitySubject.asCurrentValuePublisher())
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
|
||||
@@ -12,13 +12,13 @@ import Combine
|
||||
|
||||
@MainActor
|
||||
class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
var clientProxy: ClientProxyMock!
|
||||
var timelineControllerFactory: TimelineControllerFactoryMock!
|
||||
var userSessionFlowCoordinator: UserSessionFlowCoordinator!
|
||||
var rootCoordinator: NavigationRootCoordinator!
|
||||
var notificationManager: NotificationManagerMock!
|
||||
var userIndicatorController: UserIndicatorControllerMock!
|
||||
let stateMachineFactory = PublishedStateMachineFactory()
|
||||
|
||||
let networkReachabilitySubject: CurrentValueSubject<NetworkMonitorReachability, Never> = .init(.reachable)
|
||||
let homeserverReachabilitySubject: CurrentValueSubject<NetworkMonitorReachability, Never> = .init(.reachable)
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
var tabCoordinator: NavigationTabCoordinator<UserSessionFlowCoordinator.HomeTab>? { rootCoordinator?.rootCoordinator as? NavigationTabCoordinator }
|
||||
@@ -28,24 +28,30 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
|
||||
override func setUp() async throws {
|
||||
cancellables.removeAll()
|
||||
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||
timelineControllerFactory = TimelineControllerFactoryMock(.init())
|
||||
|
||||
rootCoordinator = NavigationRootCoordinator()
|
||||
|
||||
notificationManager = NotificationManagerMock()
|
||||
let clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||
clientProxy.homeserverReachabilityPublisher = homeserverReachabilitySubject.asCurrentValuePublisher()
|
||||
|
||||
let networkMonitor = NetworkMonitorMock.default
|
||||
networkMonitor.reachabilityPublisher = networkReachabilitySubject.asCurrentValuePublisher()
|
||||
let appMediator = AppMediatorMock.default
|
||||
appMediator.networkMonitor = networkMonitor
|
||||
|
||||
userIndicatorController = UserIndicatorControllerMock()
|
||||
|
||||
let flowParameters = CommonFlowParameters(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||
bugReportService: BugReportServiceMock(.init()),
|
||||
elementCallService: ElementCallServiceMock(.init()),
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()),
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
appMediator: AppMediatorMock.default,
|
||||
appMediator: appMediator,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
notificationManager: notificationManager,
|
||||
userIndicatorController: userIndicatorController,
|
||||
notificationManager: NotificationManagerMock(),
|
||||
stateMachineFactory: stateMachineFactory)
|
||||
|
||||
userSessionFlowCoordinator = UserSessionFlowCoordinator(isNewLogin: false,
|
||||
@@ -56,6 +62,8 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
userSessionFlowCoordinator.start()
|
||||
}
|
||||
|
||||
// MARK: Navigation
|
||||
|
||||
func testInitialState() async throws {
|
||||
XCTAssertNotNil(chatsSplitCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
@@ -154,6 +162,51 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
|
||||
}
|
||||
|
||||
// MARK: Indicators
|
||||
|
||||
func testReachabilityIndicators() async throws {
|
||||
// Given a flow in its initial state.
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then no reachability indicators should be shown.
|
||||
XCTAssertFalse(userIndicatorController.submitIndicatorDelayCalled)
|
||||
XCTAssertEqual(retractReachabilityIndicatorCallsCount, 1) // The initial state removes the indicator.
|
||||
|
||||
// When the homeserver becomes unreachable.
|
||||
homeserverReachabilitySubject.send(.unreachable)
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then a server unreachable indicator should be shown.
|
||||
XCTAssertEqual(userIndicatorController.submitIndicatorDelayCallsCount, 1)
|
||||
XCTAssertEqual(userIndicatorController.submitIndicatorDelayReceivedArguments?.indicator.title, L10n.commonServerUnreachable)
|
||||
XCTAssertEqual(retractReachabilityIndicatorCallsCount, 1)
|
||||
|
||||
// When the network also becomes unreachable.
|
||||
networkReachabilitySubject.send(.unreachable)
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the server unreachable indicator should be replaced with an offline indicator.
|
||||
XCTAssertEqual(userIndicatorController.submitIndicatorDelayCallsCount, 2)
|
||||
XCTAssertEqual(userIndicatorController.submitIndicatorDelayReceivedArguments?.indicator.title, L10n.commonOffline)
|
||||
XCTAssertEqual(retractReachabilityIndicatorCallsCount, 1)
|
||||
|
||||
// When the homeserver becomes reachable again.
|
||||
homeserverReachabilitySubject.send(.reachable)
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the indicator should be hidden even if the network isn't reachable.
|
||||
XCTAssertEqual(userIndicatorController.submitIndicatorDelayCallsCount, 2)
|
||||
XCTAssertEqual(retractReachabilityIndicatorCallsCount, 2)
|
||||
|
||||
// When the network becomes reachable again.
|
||||
networkReachabilitySubject.send(.reachable)
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then nothing else should happen.
|
||||
XCTAssertEqual(userIndicatorController.submitIndicatorDelayCallsCount, 2)
|
||||
XCTAssertEqual(retractReachabilityIndicatorCallsCount, 3)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func process(route: AppRoute,
|
||||
@@ -179,4 +232,12 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
try await deferredUserSession?.fulfill()
|
||||
try await deferredChatsState?.fulfill()
|
||||
}
|
||||
|
||||
/// Other services retract indicators, so this filters based on the reachability ID.
|
||||
private var retractReachabilityIndicatorCallsCount: Int {
|
||||
userIndicatorController
|
||||
.retractIndicatorWithIdReceivedInvocations
|
||||
.filter { $0 == "io.element.elementx.reachability.notification" }
|
||||
.count
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user