Change user display name and avatar (#1718)

* Fixes #1715, #1716 - Add new user profile editing screen

* Bump SDK, adopt new `client.removeAvatar()` method

* Address PR review comments
This commit is contained in:
Stefan Ceriu
2023-09-15 16:20:33 +03:00
committed by GitHub
parent b3a0680d64
commit fe711a9a40
20 changed files with 700 additions and 69 deletions
@@ -43,12 +43,14 @@ class ClientProxy: ClientProxyProtocol {
private let roomListRecencyOrderingAllowedEventTypes = ["m.room.message", "m.room.encrypted", "m.sticker"]
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 let userAvatarURLSubject = CurrentValueSubject<URL?, Never>(nil)
var userAvatarURL: CurrentValuePublisher<URL?, Never> {
userAvatarURLSubject.asCurrentValuePublisher()
}
private let userDisplayNameSubject = CurrentValueSubject<String?, Never>(nil)
var userDisplayName: CurrentValuePublisher<String?, Never> {
userDisplayNameSubject.asCurrentValuePublisher()
}
private var cancellables = Set<AnyCancellable>()
@@ -279,26 +281,74 @@ class ClientProxy: ClientProxyProtocol {
backgroundTaskService: backgroundTaskService)
}
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
func loadUserDisplayName() async -> Result<Void, ClientProxyError> {
await Task.dispatch(on: clientQueue) {
do {
let displayName = try self.client.displayName()
return .success(displayName)
self.userDisplayNameSubject.send(displayName)
return .success(())
} catch {
return .failure(.failedRetrievingDisplayName)
return .failure(.failedRetrievingUserDisplayName)
}
}
}
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError> {
await Task.dispatch(on: clientQueue) {
do {
try self.client.setDisplayName(name: name)
Task {
await self.loadUserDisplayName()
}
return .success(())
} catch {
return .failure(.failedSettingUserDisplayName)
}
}
}
func loadUserAvatarURL() async {
func loadUserAvatarURL() async -> Result<Void, ClientProxyError> {
await Task.dispatch(on: clientQueue) {
do {
let urlString = try self.client.avatarUrl()
self.loadCachedAvatarURLTask?.cancel()
self.avatarURLSubject.value = urlString.flatMap(URL.init)
self.userAvatarURLSubject.send(urlString.flatMap(URL.init))
return .success(())
} catch {
MXLog.error("Failed fetching the user avatar url: \(error)")
return
return .failure(.failedRetrievingUserAvatarURL)
}
}
}
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError> {
await Task.dispatch(on: .global()) {
guard case let .image(imageURL, _, _) = media, let mimeType = media.mimeType else {
return .failure(.failedSettingUserAvatar)
}
do {
let data = try Data(contentsOf: imageURL)
try self.client.uploadAvatar(mimeType: mimeType, data: [UInt8](data))
Task {
await self.loadUserAvatarURL()
}
return .success(())
} catch {
return .failure(.failedSettingUserAvatar)
}
}
}
func removeUserAvatar() async -> Result<Void, ClientProxyError> {
await Task.dispatch(on: .global()) {
do {
try self.client.removeAvatar()
Task {
await self.loadUserAvatarURL()
}
return .success(())
} catch {
return .failure(.failedSettingUserAvatar)
}
}
}
@@ -381,7 +431,7 @@ class ClientProxy: ClientProxyProtocol {
}
}
guard !Task.isCancelled else { return }
self.avatarURLSubject.value = urlString.flatMap(URL.init)
self.userAvatarURLSubject.value = urlString.flatMap(URL.init)
}
}
@@ -39,7 +39,9 @@ enum ClientProxyLoadingState {
enum ClientProxyError: Error {
case failedCreatingRoom
case failedRetrievingDirectRoom
case failedRetrievingDisplayName
case failedRetrievingUserDisplayName
case failedRetrievingUserAvatarURL
case failedSettingUserDisplayName
case failedRetrievingAccountData
case failedSettingAccountData
case failedRetrievingSessionVerificationController
@@ -48,6 +50,7 @@ enum ClientProxyError: Error {
case failedUploadingMedia(MatrixErrorCode)
case failedSearchingUsers
case failedGettingUserProfile
case failedSettingUserAvatar
}
enum SlidingSyncConstants {
@@ -77,8 +80,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var deviceID: String? { get }
var homeserver: String { get }
var userDisplayName: CurrentValuePublisher<String?, Never> { get }
var avatarURLPublisher: AnyPublisher<URL?, Never> { get }
var userAvatarURL: CurrentValuePublisher<URL?, Never> { get }
var restorationToken: RestorationToken? { get }
@@ -104,9 +109,15 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol?
func loadUserDisplayName() async -> Result<String, ClientProxyError>
@discardableResult func loadUserDisplayName() async -> Result<Void, ClientProxyError>
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError>
func loadUserAvatarURL() async
@discardableResult func loadUserAvatarURL() async -> Result<Void, ClientProxyError>
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError>
func removeUserAvatar() async -> Result<Void, ClientProxyError>
func accountDataEvent<Content: Decodable>(type: String) async -> Result<Content?, ClientProxyError>
@@ -32,7 +32,9 @@ class MockClientProxy: ClientProxyProtocol {
var inviteSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
var avatarURLPublisher: AnyPublisher<URL?, Never> { Empty().eraseToAnyPublisher() }
var userAvatarURL: CurrentValuePublisher<URL?, Never> { CurrentValueSubject<URL?, Never>(nil).asCurrentValuePublisher() }
var userDisplayName: CurrentValuePublisher<String?, Never> { CurrentValueSubject<String?, Never>("User display name").asCurrentValuePublisher() }
var notificationSettings: NotificationSettingsProxyProtocol = NotificationSettingsProxyMock(with: .init())
@@ -41,8 +43,6 @@ class MockClientProxy: ClientProxyProtocol {
self.deviceID = deviceID
self.roomSummaryProvider = roomSummaryProvider
}
func loadUserAvatarURL() async { }
func startSync() { }
@@ -87,8 +87,24 @@ class MockClientProxy: ClientProxyProtocol {
}
}
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
.success("User display name")
func loadUserDisplayName() async -> Result<Void, ClientProxyError> {
.failure(.failedRetrievingUserDisplayName)
}
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError> {
.failure(.failedSettingUserDisplayName)
}
func loadUserAvatarURL() async -> Result<Void, ClientProxyError> {
.failure(.failedRetrievingUserAvatarURL)
}
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError> {
.failure(.failedSettingUserAvatar)
}
func removeUserAvatar() async -> Result<Void, ClientProxyError> {
.failure(.failedSettingUserAvatar)
}
func accountDataEvent<Content>(type: String) async -> Result<Content?, ClientProxyError> where Content: Decodable {