mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
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:
@@ -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 {
|
||||||
|
case .startedUpdating:
|
||||||
|
ServiceLocator.shared.userIndicatorController.submitIndicator(.init(id: identifier, type: .toast(progress: .indeterminate), title: L10n.commonSyncing, persistent: true))
|
||||||
|
case .receivedSyncUpdate:
|
||||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier)
|
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(identifier)
|
||||||
self?.initialSyncObserver?.cancel()
|
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 {
|
||||||
|
if let delay {
|
||||||
|
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)
|
retractIndicatorWithId(indicator.id)
|
||||||
indicatorQueue.append(indicator)
|
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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user