Cached avatar support (from Rust) (#633)

* loading a cached avatar at first, and then actually doing the request so that the avatar can still be displayed without connection

* improved the code

* fix + changelog

* policy on how to fetch the avatar url added

* code improvements

* addressed al PR comments

* improved the error handling

* improved the async task code

* fixed a typo

* fixed another typo
This commit is contained in:
Mauro
2023-03-08 18:50:06 +01:00
committed by GitHub
parent c67c4e362e
commit fb27efee10
10 changed files with 62 additions and 31 deletions
+2 -1
View File
@@ -49,7 +49,8 @@ let allowList = ["stefanceriu",
"manuroe",
"gileluard",
"phlniji",
"aringenbach"]
"aringenbach",
"Velin92"]
let requiresSignOff = !allowList.contains(where: {
$0.caseInsensitiveCompare(danger.github.pullRequest.user.login) == .orderedSame
@@ -95,8 +95,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : {
"revision" : "32645162ea39badc586d610d2ada6bbc52f398ba",
"version" : "1.0.43-alpha"
"revision" : "8d7ace5140d0f024b9a03939cdff29c837dc5645",
"version" : "1.0.45-alpha"
}
},
{
@@ -30,7 +30,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
var callback: ((HomeScreenViewModelAction) -> Void)?
// swiftlint:disable:next function_body_length cyclomatic_complexity
// swiftlint:disable:next function_body_length
init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) {
self.userSession = userSession
self.attributedStringBuilder = attributedStringBuilder
@@ -54,6 +54,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}
}
.store(in: &cancellables)
userSession.clientProxy.avatarURLPublisher
.weakAssign(to: \.state.userAvatarURL, on: self)
.store(in: &cancellables)
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider else {
MXLog.error("Room summary provider unavailable")
@@ -91,9 +95,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
// Delay user profile detail loading until after the initial room list loads
if roomListMode == .rooms {
Task {
if case let .success(url) = await userSession.clientProxy.loadUserAvatarURL() {
self.state.userAvatarURL = url
}
await userSession.clientProxy.loadUserAvatarURL()
}
Task {
@@ -43,11 +43,13 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
ServiceLocator.shared.settings.$timelineStyle
.weakAssign(to: \.state.bindings.timelineStyle, on: self)
.store(in: &cancellables)
userSession.clientProxy.avatarURLPublisher
.weakAssign(to: \.state.userAvatarURL, on: self)
.store(in: &cancellables)
Task {
if case let .success(userAvatarURL) = await userSession.clientProxy.loadUserAvatarURL() {
state.userAvatarURL = userAvatarURL
}
await userSession.clientProxy.loadUserAvatarURL()
}
Task {
@@ -58,6 +58,15 @@ class ClientProxy: ClientProxyProtocol {
var allRoomsSlidingSyncView: SlidingSyncList?
var allRoomsSummaryProvider: RoomSummaryProviderProtocol?
private var loadCachedAvatarURLTask: Task<Void, Never>?
private let avatarURLSubject = CurrentValueSubject<URL?, Never>(nil)
var avatarURLPublisher: AnyPublisher<URL?, Never> {
avatarURLSubject
.removeDuplicates()
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
private var cancellables = Set<AnyCancellable>()
private var visibleRoomsViewProxyStateObservationToken: AnyCancellable?
@@ -82,6 +91,8 @@ class ClientProxy: ClientProxyProtocol {
client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self))
configureSlidingSync()
loadUserAvatarURLFromCache()
}
var userID: String {
@@ -147,6 +158,7 @@ class ClientProxy: ClientProxyProtocol {
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
await Task.dispatch(on: clientQueue) {
do {
self.loadCachedAvatarURLTask?.cancel()
let displayName = try self.client.displayName()
return .success(displayName)
} catch {
@@ -154,24 +166,19 @@ class ClientProxy: ClientProxyProtocol {
}
}
}
func loadUserAvatarURL() async -> Result<URL, ClientProxyError> {
func loadUserAvatarURL() async {
await Task.dispatch(on: clientQueue) {
do {
let urlString = try self.client.avatarUrl()
guard let url = URL(string: urlString) else {
MXLog.error("Invalid avatar URL string: \(String(describing: urlString))")
return .failure(.failedRetrievingAvatarURL)
}
return .success(url)
self.avatarURLSubject.value = urlString.flatMap(URL.init)
} catch {
return .failure(.failedRetrievingAvatarURL)
MXLog.error("Failed fetching the user avatar url: \(error)")
return
}
}
}
func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {
await Task.dispatch(on: clientQueue) {
.failure(.failedRetrievingAccountData)
@@ -232,6 +239,21 @@ class ClientProxy: ClientProxyProtocol {
}
// MARK: Private
private func loadUserAvatarURLFromCache() {
loadCachedAvatarURLTask = Task {
let urlString = await Task.dispatch(on: clientQueue) {
do {
return try self.client.cachedAvatarUrl()
} catch {
MXLog.error("Failed to look for the avatar url in the cache: \(error)")
return nil
}
}
guard !Task.isCancelled else { return }
self.avatarURLSubject.value = urlString.flatMap(URL.init)
}
}
private func restartSync() {
MXLog.info("Restarting sync")
@@ -24,7 +24,6 @@ enum ClientProxyCallback {
}
enum ClientProxyError: Error {
case failedRetrievingAvatarURL
case failedRetrievingDisplayName
case failedRetrievingAccountData
case failedSettingAccountData
@@ -72,6 +71,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var homeserver: String { get }
var avatarURLPublisher: AnyPublisher<URL?, Never> { get }
var restorationToken: RestorationToken? { get }
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }
@@ -85,9 +86,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
func loadUserDisplayName() async -> Result<String, ClientProxyError>
func loadUserAvatarURL() async -> Result<URL, ClientProxyError>
func loadUserAvatarURL() async
func accountDataEvent<Content: Decodable>(type: String) async -> Result<Content?, ClientProxyError>
func setAccountData<Content: Encodable>(content: Content, type: String) async -> Result<Void, ClientProxyError>
@@ -29,11 +29,15 @@ class MockClientProxy: ClientProxyProtocol {
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
var allRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
var avatarURLPublisher: AnyPublisher<URL?, Never> { Empty().eraseToAnyPublisher() }
internal init(userID: String, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) {
self.userID = userID
visibleRoomsSummaryProvider = roomSummaryProvider
}
func loadUserAvatarURL() async { }
func startSync() { }
@@ -56,10 +60,6 @@ class MockClientProxy: ClientProxyProtocol {
.success("User display name")
}
func loadUserAvatarURL() async -> Result<URL, ClientProxyError> {
.failure(.failedRetrievingAvatarURL)
}
func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {
.failure(.failedRetrievingAccountData)
}
@@ -83,7 +83,9 @@ private class MockMediaLoadingClient: ClientProtocol {
func setDisplayName(name: String) throws { }
func avatarUrl() throws -> String { fatalError() }
func avatarUrl() throws -> String? { fatalError() }
func cachedAvatarUrl() throws -> String? { fatalError() }
func deviceId() throws -> String { fatalError() }
+1
View File
@@ -0,0 +1 @@
Avatar url is now cached on the rust side.
+1 -1
View File
@@ -40,7 +40,7 @@ include:
packages:
MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.0.43-alpha
exactVersion: 1.0.45-alpha
# path: ../matrix-rust-sdk
DesignKit:
path: DesignKit