mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 03:06:37 +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",
|
"manuroe",
|
||||||
"gileluard",
|
"gileluard",
|
||||||
"phlniji",
|
"phlniji",
|
||||||
"aringenbach"]
|
"aringenbach",
|
||||||
|
"Velin92"]
|
||||||
|
|
||||||
let requiresSignOff = !allowList.contains(where: {
|
let requiresSignOff = !allowList.contains(where: {
|
||||||
$0.caseInsensitiveCompare(danger.github.pullRequest.user.login) == .orderedSame
|
$0.caseInsensitiveCompare(danger.github.pullRequest.user.login) == .orderedSame
|
||||||
|
|||||||
@@ -95,8 +95,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
"location" : "https://github.com/matrix-org/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "32645162ea39badc586d610d2ada6bbc52f398ba",
|
"revision" : "8d7ace5140d0f024b9a03939cdff29c837dc5645",
|
||||||
"version" : "1.0.43-alpha"
|
"version" : "1.0.45-alpha"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
var callback: ((HomeScreenViewModelAction) -> Void)?
|
var callback: ((HomeScreenViewModelAction) -> Void)?
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length cyclomatic_complexity
|
// swiftlint:disable:next function_body_length
|
||||||
init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) {
|
init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
self.attributedStringBuilder = attributedStringBuilder
|
self.attributedStringBuilder = attributedStringBuilder
|
||||||
@@ -54,6 +54,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
userSession.clientProxy.avatarURLPublisher
|
||||||
|
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider else {
|
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider else {
|
||||||
MXLog.error("Room summary provider unavailable")
|
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
|
// Delay user profile detail loading until after the initial room list loads
|
||||||
if roomListMode == .rooms {
|
if roomListMode == .rooms {
|
||||||
Task {
|
Task {
|
||||||
if case let .success(url) = await userSession.clientProxy.loadUserAvatarURL() {
|
await userSession.clientProxy.loadUserAvatarURL()
|
||||||
self.state.userAvatarURL = url
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
@@ -43,11 +43,13 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
ServiceLocator.shared.settings.$timelineStyle
|
ServiceLocator.shared.settings.$timelineStyle
|
||||||
.weakAssign(to: \.state.bindings.timelineStyle, on: self)
|
.weakAssign(to: \.state.bindings.timelineStyle, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
userSession.clientProxy.avatarURLPublisher
|
||||||
|
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
if case let .success(userAvatarURL) = await userSession.clientProxy.loadUserAvatarURL() {
|
await userSession.clientProxy.loadUserAvatarURL()
|
||||||
state.userAvatarURL = userAvatarURL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
|
|
||||||
var allRoomsSlidingSyncView: SlidingSyncList?
|
var allRoomsSlidingSyncView: SlidingSyncList?
|
||||||
var allRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
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 cancellables = Set<AnyCancellable>()
|
||||||
private var visibleRoomsViewProxyStateObservationToken: AnyCancellable?
|
private var visibleRoomsViewProxyStateObservationToken: AnyCancellable?
|
||||||
@@ -82,6 +91,8 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self))
|
client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self))
|
||||||
|
|
||||||
configureSlidingSync()
|
configureSlidingSync()
|
||||||
|
|
||||||
|
loadUserAvatarURLFromCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
var userID: String {
|
var userID: String {
|
||||||
@@ -147,6 +158,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
|
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
|
||||||
await Task.dispatch(on: clientQueue) {
|
await Task.dispatch(on: clientQueue) {
|
||||||
do {
|
do {
|
||||||
|
self.loadCachedAvatarURLTask?.cancel()
|
||||||
let displayName = try self.client.displayName()
|
let displayName = try self.client.displayName()
|
||||||
return .success(displayName)
|
return .success(displayName)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -154,24 +166,19 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadUserAvatarURL() async -> Result<URL, ClientProxyError> {
|
func loadUserAvatarURL() async {
|
||||||
await Task.dispatch(on: clientQueue) {
|
await Task.dispatch(on: clientQueue) {
|
||||||
do {
|
do {
|
||||||
let urlString = try self.client.avatarUrl()
|
let urlString = try self.client.avatarUrl()
|
||||||
|
self.avatarURLSubject.value = urlString.flatMap(URL.init)
|
||||||
guard let url = URL(string: urlString) else {
|
|
||||||
MXLog.error("Invalid avatar URL string: \(String(describing: urlString))")
|
|
||||||
return .failure(.failedRetrievingAvatarURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return .success(url)
|
|
||||||
} catch {
|
} 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 {
|
func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {
|
||||||
await Task.dispatch(on: clientQueue) {
|
await Task.dispatch(on: clientQueue) {
|
||||||
.failure(.failedRetrievingAccountData)
|
.failure(.failedRetrievingAccountData)
|
||||||
@@ -232,6 +239,21 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// 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() {
|
private func restartSync() {
|
||||||
MXLog.info("Restarting sync")
|
MXLog.info("Restarting sync")
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ enum ClientProxyCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ClientProxyError: Error {
|
enum ClientProxyError: Error {
|
||||||
case failedRetrievingAvatarURL
|
|
||||||
case failedRetrievingDisplayName
|
case failedRetrievingDisplayName
|
||||||
case failedRetrievingAccountData
|
case failedRetrievingAccountData
|
||||||
case failedSettingAccountData
|
case failedSettingAccountData
|
||||||
@@ -72,6 +71,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
var homeserver: String { get }
|
var homeserver: String { get }
|
||||||
|
|
||||||
|
var avatarURLPublisher: AnyPublisher<URL?, Never> { get }
|
||||||
|
|
||||||
var restorationToken: RestorationToken? { get }
|
var restorationToken: RestorationToken? { get }
|
||||||
|
|
||||||
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }
|
var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? { get }
|
||||||
@@ -85,9 +86,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
|
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
|
||||||
|
|
||||||
func loadUserDisplayName() async -> Result<String, ClientProxyError>
|
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 accountDataEvent<Content: Decodable>(type: String) async -> Result<Content?, ClientProxyError>
|
||||||
|
|
||||||
func setAccountData<Content: Encodable>(content: Content, type: String) async -> Result<Void, 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 visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||||
|
|
||||||
var allRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
var allRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
|
||||||
|
|
||||||
|
var avatarURLPublisher: AnyPublisher<URL?, Never> { Empty().eraseToAnyPublisher() }
|
||||||
|
|
||||||
internal init(userID: String, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) {
|
internal init(userID: String, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) {
|
||||||
self.userID = userID
|
self.userID = userID
|
||||||
visibleRoomsSummaryProvider = roomSummaryProvider
|
visibleRoomsSummaryProvider = roomSummaryProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadUserAvatarURL() async { }
|
||||||
|
|
||||||
func startSync() { }
|
func startSync() { }
|
||||||
|
|
||||||
@@ -56,10 +60,6 @@ class MockClientProxy: ClientProxyProtocol {
|
|||||||
.success("User display name")
|
.success("User display name")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadUserAvatarURL() async -> Result<URL, ClientProxyError> {
|
|
||||||
.failure(.failedRetrievingAvatarURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {
|
func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {
|
||||||
.failure(.failedRetrievingAccountData)
|
.failure(.failedRetrievingAccountData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ private class MockMediaLoadingClient: ClientProtocol {
|
|||||||
|
|
||||||
func setDisplayName(name: String) throws { }
|
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() }
|
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:
|
packages:
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/matrix-org/matrix-rust-components-swift
|
url: https://github.com/matrix-org/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.43-alpha
|
exactVersion: 1.0.45-alpha
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
DesignKit:
|
DesignKit:
|
||||||
path: DesignKit
|
path: DesignKit
|
||||||
|
|||||||
Reference in New Issue
Block a user