mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-01 01:03:01 +00:00
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:
+2
-1
@@ -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() }
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Avatar url is now cached on the rust side.
|
||||
+1
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user