Various tweaks (#1129)

* Fixes #1121 - Hide the loading indicator after the logout task finishes

* Manually restart the sync only when entering the `error` state

* Use stopSync instead of the roomListService directly on client deinit

* Replace WeakClientProxyWrapper with callback based delegate

* Fix homescreen user avatar not automatically updating

* Replace default Build SDK profile with reldbg, which is fast but also doesn't crash

* Always show the loading indicator when the room list is not in a `running` state

* Implement delayed user indicator presentations through the normal API

* Fix the unit tests

* Replace UserIndicatorController delayedIndicators dictionary with a plain set
This commit is contained in:
Stefan Ceriu
2023-06-22 15:04:20 +03:00
committed by GitHub
parent 8e8bb7a3e7
commit e65a2d2431
15 changed files with 103 additions and 89 deletions
+4 -4
View File
@@ -325,7 +325,6 @@
804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; }; 804C15D8ADE0EA7A5268F58A /* OverridableAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD1C10E4957CB791FE0B8 /* OverridableAvatarImage.swift */; };
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; }; 80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; }; 80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */; };
8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */; };
81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; }; 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F36C0A6D59717193F49EA986 /* UserSessionTests.swift */; };
8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; }; 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; };
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; }; 828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; };
@@ -426,6 +425,7 @@
A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; }; A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; };
A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; }; A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; };
A1D4033881320C9EB88196E6 /* ServerConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */; }; A1D4033881320C9EB88196E6 /* ServerConfirmationScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */; };
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */; };
A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */; }; A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */; };
A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; };
A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; }; A2434D4DFB49A68E5CD0F53C /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; };
@@ -775,7 +775,6 @@
0C88046D6A070D9827181C4D /* OnboardingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingUITests.swift; sourceTree = "<group>"; }; 0C88046D6A070D9827181C4D /* OnboardingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingUITests.swift; sourceTree = "<group>"; };
0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; }; 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; };
0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0D8F620C8B314840D8602E3F /* NSE.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NSE.appex; sourceTree = BUILT_PRODUCTS_DIR; };
0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationControllerTests.swift; sourceTree = "<group>"; };
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; }; 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; };
0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenUITests.swift; sourceTree = "<group>"; }; 0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenUITests.swift; sourceTree = "<group>"; };
0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = "<group>"; }; 0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = "<group>"; };
@@ -835,6 +834,7 @@
227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; }; 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = "<group>"; }; 23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = "<group>"; };
24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = "<group>"; }; 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = "<group>"; };
2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerTests.swift; sourceTree = "<group>"; };
248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyMock.swift; sourceTree = "<group>"; }; 248649EBA5BC33DB93698734 /* SessionVerificationControllerProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxyMock.swift; sourceTree = "<group>"; };
24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModel.swift; sourceTree = "<group>"; }; 24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModel.swift; sourceTree = "<group>"; };
24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@@ -2363,7 +2363,7 @@
2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */, 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */,
1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */, 1734A445A58ED855B977A0A8 /* TracingConfigurationTests.swift */,
EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */, EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */,
0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */, 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */,
BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */, BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */,
53280D2292E6C9C7821773FD /* UserSession */, 53280D2292E6C9C7821773FD /* UserSession */,
70C5B842301AC281DF374E41 /* Extensions */, 70C5B842301AC281DF374E41 /* Extensions */,
@@ -3874,8 +3874,8 @@
AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */, AF33B9044498211C3D82F1E1 /* UNTextInputNotificationResponse+Creator.swift in Sources */,
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */, 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */,
E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */, E313BDD2B8813144139B2E00 /* UserDiscoveryServiceTest.swift in Sources */,
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */,
08248D02BACA75CDC3B39A96 /* UserNotificationCenterSpy.swift in Sources */, 08248D02BACA75CDC3B39A96 /* UserNotificationCenterSpy.swift in Sources */,
8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */,
04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */, 04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */,
81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */, 81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */,
99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */, 99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */,
@@ -52,8 +52,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
private var appDelegateObserver: AnyCancellable? private var appDelegateObserver: AnyCancellable?
private var userSessionObserver: AnyCancellable? private var userSessionObserver: AnyCancellable?
private var clientProxyObserver: AnyCancellable?
private var networkMonitorObserver: AnyCancellable? private var networkMonitorObserver: AnyCancellable?
private var initialSyncObserver: AnyCancellable?
private var backgroundRefreshSyncObserver: AnyCancellable? private var backgroundRefreshSyncObserver: AnyCancellable?
let notificationManager: NotificationManagerProtocol let notificationManager: NotificationManagerProtocol
@@ -355,15 +355,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
showLoadingIndicator() showLoadingIndicator()
defer {
hideLoadingIndicator()
}
stopSync() stopSync()
userSessionFlowCoordinator?.stop() userSessionFlowCoordinator?.stop()
guard !isSoft else { guard !isSoft else {
stateMachine.processEvent(.completedSigningOut(isSoft: isSoft)) stateMachine.processEvent(.completedSigningOut(isSoft: isSoft))
hideLoadingIndicator()
return return
} }
@@ -380,6 +377,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
ServiceLocator.shared.analytics.resetConsentState() ServiceLocator.shared.analytics.resetConsentState()
stateMachine.processEvent(.completedSigningOut(isSoft: isSoft)) stateMachine.processEvent(.completedSigningOut(isSoft: isSoft))
hideLoadingIndicator()
} }
} }
@@ -519,6 +518,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
backgroundAppRefreshTask?.setTaskCompleted(success: true) backgroundAppRefreshTask?.setTaskCompleted(success: true)
backgroundAppRefreshTask = nil backgroundAppRefreshTask = nil
clientProxyObserver = nil
} }
private func startSync() { private func startSync() {
@@ -532,15 +533,18 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
let identifier = "StaleDataIndicator" let identifier = "StaleDataIndicator"
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true)) clientProxyObserver = userSession.clientProxy
initialSyncObserver = userSession.clientProxy
.callbacks .callbacks
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.filter(\.isSyncUpdate) .sink { action in
.sink { [weak self] _ in switch action {
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier) case .startedUpdating:
self?.initialSyncObserver?.cancel() ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true))
case .receivedSyncUpdate:
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier)
default:
break
}
} }
} }
@@ -1210,19 +1210,19 @@ class UserIndicatorControllerMock: UserIndicatorControllerProtocol {
//MARK: - submitIndicator //MARK: - submitIndicator
var submitIndicatorCallsCount = 0 var submitIndicatorDelayCallsCount = 0
var submitIndicatorCalled: Bool { var submitIndicatorDelayCalled: Bool {
return submitIndicatorCallsCount > 0 return submitIndicatorDelayCallsCount > 0
} }
var submitIndicatorReceivedIndicator: UserIndicator? var submitIndicatorDelayReceivedArguments: (indicator: UserIndicator, delay: Duration?)?
var submitIndicatorReceivedInvocations: [UserIndicator] = [] var submitIndicatorDelayReceivedInvocations: [(indicator: UserIndicator, delay: Duration?)] = []
var submitIndicatorClosure: ((UserIndicator) -> Void)? var submitIndicatorDelayClosure: ((UserIndicator, Duration?) -> Void)?
func submitIndicator(_ indicator: UserIndicator) { func submitIndicator(_ indicator: UserIndicator, delay: Duration?) {
submitIndicatorCallsCount += 1 submitIndicatorDelayCallsCount += 1
submitIndicatorReceivedIndicator = indicator submitIndicatorDelayReceivedArguments = (indicator: indicator, delay: delay)
submitIndicatorReceivedInvocations.append(indicator) submitIndicatorDelayReceivedInvocations.append((indicator: indicator, delay: delay))
submitIndicatorClosure?(indicator) submitIndicatorDelayClosure?(indicator, delay)
} }
//MARK: - retractIndicatorWithId //MARK: - retractIndicatorWithId
@@ -20,7 +20,7 @@ import Foundation
extension UserIndicatorControllerMock { extension UserIndicatorControllerMock {
static var `default`: UserIndicatorControllerMock { static var `default`: UserIndicatorControllerMock {
let mock = UserIndicatorControllerMock() let mock = UserIndicatorControllerMock()
mock.submitIndicatorClosure = { _ in } mock.submitIndicatorDelayClosure = { _, _ in }
mock.retractIndicatorWithIdClosure = { _ in } mock.retractIndicatorWithIdClosure = { _ in }
mock.retractAllIndicatorsClosure = { } mock.retractAllIndicatorsClosure = { }
return mock return mock
@@ -21,6 +21,7 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
private var dismisalTimer: Timer? private var dismisalTimer: Timer?
private var displayTimes = [String: Date]() private var displayTimes = [String: Date]()
private var delayedIndicators = Set<String>()
var nonPersistentDisplayDuration = 2.5 var nonPersistentDisplayDuration = 2.5
var minimumDisplayDuration = 0.5 var minimumDisplayDuration = 0.5
@@ -51,12 +52,27 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
) )
} }
func submitIndicator(_ indicator: UserIndicator) { func submitIndicator(_ indicator: UserIndicator, delay: Duration?) {
if let index = indicatorQueue.firstIndex(where: { $0.id == indicator.id }) { if let index = indicatorQueue.firstIndex(where: { $0.id == indicator.id }) {
indicatorQueue[index] = indicator indicatorQueue[index] = indicator
} else { } else {
retractIndicatorWithId(indicator.id) if let delay {
indicatorQueue.append(indicator) delayedIndicators.insert(indicator.id)
Timer.scheduledTimer(withTimeInterval: Double(delay.components.seconds), repeats: false) { [weak self] _ in
guard let self else { return }
guard delayedIndicators.contains(indicator.id) else {
return
}
retractIndicatorWithId(indicator.id)
indicatorQueue.append(indicator)
delayedIndicators.remove(indicator.id)
}
} else {
retractIndicatorWithId(indicator.id)
indicatorQueue.append(indicator)
}
} }
displayTimes[indicator.id] = .now displayTimes[indicator.id] = .now
@@ -69,6 +85,8 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol
} }
func retractIndicatorWithId(_ id: String) { func retractIndicatorWithId(_ id: String) {
delayedIndicators.remove(id)
guard let displayTime = displayTimes[id], abs(displayTime.timeIntervalSinceNow) <= minimumDisplayDuration else { guard let displayTime = displayTimes[id], abs(displayTime.timeIntervalSinceNow) <= minimumDisplayDuration else {
indicatorQueue.removeAll { $0.id == id } indicatorQueue.removeAll { $0.id == id }
return return
@@ -18,19 +18,14 @@ import Foundation
// sourcery: AutoMockable // sourcery: AutoMockable
protocol UserIndicatorControllerProtocol: CoordinatorProtocol { protocol UserIndicatorControllerProtocol: CoordinatorProtocol {
func submitIndicator(_ indicator: UserIndicator) func submitIndicator(_ indicator: UserIndicator, delay: Duration?)
func retractIndicatorWithId(_ id: String) func retractIndicatorWithId(_ id: String)
func retractAllIndicators() func retractAllIndicators()
var alertInfo: AlertInfo<UUID>? { get set } var alertInfo: AlertInfo<UUID>? { get set }
} }
extension UserIndicatorControllerProtocol { extension UserIndicatorControllerProtocol {
/// Allows to submit a delayed indicator, this returns a Task so that it's also possible to cancel the action func submitIndicator(_ indicator: UserIndicator) {
func submitIndicator(_ indicator: UserIndicator, delay: Duration) -> Task<Void, Never> { submitIndicator(indicator, delay: nil)
Task { @MainActor in
try? await Task.sleep(for: delay)
guard !Task.isCancelled else { return }
submitIndicator(indicator)
}
} }
} }
@@ -19,7 +19,7 @@ import SwiftUI
struct HomeScreenUserMenuButton: View { struct HomeScreenUserMenuButton: View {
@State private var showingLogoutConfirmation = false @State private var showingLogoutConfirmation = false
let context: HomeScreenViewModel.Context @ObservedObject var context: HomeScreenViewModel.Context
var body: some View { var body: some View {
Menu { Menu {
@@ -26,7 +26,6 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
private let roomType: InviteUsersScreenRoomType private let roomType: InviteUsersScreenRoomType
private weak var userIndicatorController: UserIndicatorControllerProtocol? private weak var userIndicatorController: UserIndicatorControllerProtocol?
private let actionsSubject: PassthroughSubject<InviteUsersScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<InviteUsersScreenViewModelAction, Never> = .init()
@CancellableTask private var showLoaderTask: Task<Void, Never>?
var actions: AnyPublisher<InviteUsersScreenViewModelAction, Never> { var actions: AnyPublisher<InviteUsersScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
@@ -165,11 +164,10 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
private let userIndicatorID = UUID().uuidString private let userIndicatorID = UUID().uuidString
private func showLoader() { private func showLoader() {
showLoaderTask = userIndicatorController?.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) userIndicatorController?.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200))
} }
private func hideLoader() { private func hideLoader() {
showLoaderTask = nil
userIndicatorController?.retractIndicatorWithId(userIndicatorID) userIndicatorController?.retractIndicatorWithId(userIndicatorID)
} }
} }
@@ -21,7 +21,6 @@ typealias RoomMembersListScreenViewModelType = StateStoreViewModel<RoomMembersLi
class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMembersListScreenViewModelProtocol { class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMembersListScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private var members: [RoomMemberProxyProtocol] = [] private var members: [RoomMemberProxyProtocol] = []
@CancellableTask private var showLoaderTask: Task<Void, Never>?
var callback: ((RoomMembersListScreenViewModelAction) -> Void)? var callback: ((RoomMembersListScreenViewModelAction) -> Void)?
@@ -109,11 +108,10 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
private let userIndicatorID = UUID().uuidString private let userIndicatorID = UUID().uuidString
private func showLoader() { private func showLoader() {
showLoaderTask = ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200)) ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), delay: .milliseconds(200))
} }
private func hideLoader() { private func hideLoader() {
showLoaderTask = nil
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(userIndicatorID) ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(userIndicatorID)
} }
} }
@@ -30,7 +30,6 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private let roomProxy: RoomProxyProtocol private let roomProxy: RoomProxyProtocol
private let timelineController: RoomTimelineControllerProtocol private let timelineController: RoomTimelineControllerProtocol
private unowned let userIndicatorController: UserIndicatorControllerProtocol private unowned let userIndicatorController: UserIndicatorControllerProtocol
private var loadingTask: Task<Void, Never>?
init(timelineController: RoomTimelineControllerProtocol, init(timelineController: RoomTimelineControllerProtocol,
mediaProvider: MediaProviderProtocol, mediaProvider: MediaProviderProtocol,
@@ -512,9 +511,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private func handleTappedUser(userID: String) async { private func handleTappedUser(userID: String) async {
// This is generally fast but it could take some time for rooms with thousands of users on first load // This is generally fast but it could take some time for rooms with thousands of users on first load
// Show a loader only if it takes more than 0.1 seconds // Show a loader only if it takes more than 0.1 seconds
loadingTask = showLoadingIndicator(with: .milliseconds(100)) showLoadingIndicator(with: .milliseconds(100))
let result = await roomProxy.getMember(userID: userID) let result = await roomProxy.getMember(userID: userID)
loadingTask?.cancel()
hideLoadingIndicator() hideLoadingIndicator()
switch result { switch result {
@@ -544,7 +542,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private static let loadingIndicatorIdentifier = "RoomScreenLoadingIndicator" private static let loadingIndicatorIdentifier = "RoomScreenLoadingIndicator"
private func showLoadingIndicator(with delay: Duration) -> Task<Void, Never> { private func showLoadingIndicator(with delay: Duration) {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier, userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true), type: .modal(progress: .indeterminate, interactiveDismissDisabled: true),
title: L10n.commonLoading, title: L10n.commonLoading,
@@ -19,26 +19,6 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
import UIKit import UIKit
private class WeakClientProxyWrapper: ClientDelegate {
private weak var clientProxy: ClientProxy?
init(clientProxy: ClientProxy) {
self.clientProxy = clientProxy
}
// MARK: - ClientDelegate
func didReceiveAuthError(isSoftLogout: Bool) {
MXLog.error("Received authentication error, softlogout=\(isSoftLogout)")
clientProxy?.didReceiveAuthError(isSoftLogout: isSoftLogout)
}
func didRefreshTokens() {
MXLog.info("The session has updated tokens.")
clientProxy?.updateRestorationToken()
}
}
class ClientProxy: ClientProxyProtocol { class ClientProxy: ClientProxyProtocol {
private let client: ClientProtocol private let client: ClientProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol
@@ -67,10 +47,8 @@ class ClientProxy: ClientProxyProtocol {
private var visibleRoomsListProxyStateObservationToken: AnyCancellable? private var visibleRoomsListProxyStateObservationToken: AnyCancellable?
deinit { deinit {
// These need to be inlined instead of using stopSync()
// as we can't call async methods safely from deinit
client.setDelegate(delegate: nil) client.setDelegate(delegate: nil)
try? roomListService?.stopSync() stopSync()
} }
let callbacks = PassthroughSubject<ClientProxyCallback, Never>() let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
@@ -82,8 +60,11 @@ class ClientProxy: ClientProxyProtocol {
mediaLoader = MediaLoader(client: client, clientQueue: clientQueue) mediaLoader = MediaLoader(client: client, clientQueue: clientQueue)
let delegate = WeakClientProxyWrapper(clientProxy: self) client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in
client.setDelegate(delegate: delegate) self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout))
} tokenRefreshCallback: { [weak self] in
self?.callbacks.send(.updateRestorationToken)
})
await configureRoomListService() await configureRoomListService()
@@ -391,19 +372,25 @@ class ClientProxy: ClientProxyProtocol {
MXLog.info("Received room list update: \(state)") MXLog.info("Received room list update: \(state)")
// Restart the room list sync on every error for now // Restart the room list sync on every error for now
if state == .terminated { if state == .error {
self.restartSync() self.restartSync()
} }
// The invites are available only when entering `running`
if state == .running { if state == .running {
self.callbacks.send(.receivedSyncUpdate)
Task { Task {
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms // Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
await self.inviteSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.invites(listener:), await self.inviteSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.invites(listener:),
entriesLoadingStateFunction: nil) entriesLoadingStateFunction: nil)
} }
} }
// Anything that's not `running` is interpreted as "Loading data"
if state == .running {
self.callbacks.send(.receivedSyncUpdate)
} else {
self.callbacks.send(.startedUpdating)
}
}) })
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService, roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
@@ -434,14 +421,6 @@ class ClientProxy: ClientProxyProtocol {
return (nil, nil) return (nil, nil)
} }
} }
fileprivate func updateRestorationToken() {
callbacks.send(.updateRestorationToken)
}
fileprivate func didReceiveAuthError(isSoftLogout: Bool) {
callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout))
}
} }
extension ClientProxy: MediaLoaderProtocol { extension ClientProxy: MediaLoaderProtocol {
@@ -469,3 +448,26 @@ private class RoomListStateListenerProxy: RoomListStateListener {
onUpdateClosure(state) onUpdateClosure(state)
} }
} }
private class ClientDelegateWrapper: ClientDelegate {
private let authErrorCallback: (Bool) -> Void
private let tokenRefreshCallback: () -> Void
init(authErrorCallback: @escaping (Bool) -> Void,
tokenRefreshCallback: @escaping () -> Void) {
self.authErrorCallback = authErrorCallback
self.tokenRefreshCallback = tokenRefreshCallback
}
// MARK: - ClientDelegate
func didReceiveAuthError(isSoftLogout: Bool) {
MXLog.error("Received authentication error, softlogout=\(isSoftLogout)")
authErrorCallback(isSoftLogout)
}
func didRefreshTokens() {
MXLog.info("The session has updated tokens.")
tokenRefreshCallback()
}
}
@@ -19,6 +19,7 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
enum ClientProxyCallback { enum ClientProxyCallback {
case startedUpdating
case receivedSyncUpdate case receivedSyncUpdate
case receivedAuthError(isSoftLogout: Bool) case receivedAuthError(isSoftLogout: Bool)
case updateRestorationToken case updateRestorationToken
+1 -1
View File
@@ -25,7 +25,7 @@ struct BuildSDK: ParsableCommand {
var target: Target? var target: Target?
@Option(help: "The profile to use when building the SDK. Omit this option to build in debug mode.") @Option(help: "The profile to use when building the SDK. Omit this option to build in debug mode.")
var profile: Profile = .debug var profile: Profile = .reldbg
enum Error: LocalizedError { enum Error: LocalizedError {
case rustupOutputFailure case rustupOutputFailure
@@ -185,7 +185,7 @@ class RoomScreenViewModelTests: XCTestCase {
// Test // Test
viewModel.context.send(viewAction: .tappedOnUser(userID: "bob")) viewModel.context.send(viewAction: .tappedOnUser(userID: "bob"))
await Task.yield() await Task.yield()
XCTAssertFalse(userIndicatorControllerMock.submitIndicatorCalled) XCTAssertFalse(userIndicatorControllerMock.submitIndicatorDelayCalled)
XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1) XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1)
XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob") XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob")
} }
@@ -217,7 +217,7 @@ class RoomScreenViewModelTests: XCTestCase {
// Test // Test
viewModel.context.send(viewAction: .tappedOnUser(userID: "bob")) viewModel.context.send(viewAction: .tappedOnUser(userID: "bob"))
try? await Task.sleep(for: .milliseconds(300)) try? await Task.sleep(for: .milliseconds(300))
XCTAssert(userIndicatorControllerMock.submitIndicatorCallsCount == 1) XCTAssert(userIndicatorControllerMock.submitIndicatorDelayCallsCount == 1)
XCTAssert(userIndicatorControllerMock.retractIndicatorWithIdCallsCount == 1) XCTAssert(userIndicatorControllerMock.retractIndicatorWithIdCallsCount == 1)
XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1) XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1)
XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob") XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob")