mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
vector-im/element-x-ios/issues/53 - Converted most callback based methods to async/await
This commit is contained in:
committed by
Stefan Ceriu
parent
f2fe39eca7
commit
dde7786152
@@ -70,28 +70,31 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters.userSession.callbacks.sink { [weak self] result in
|
parameters.userSession
|
||||||
switch result {
|
.callbacks
|
||||||
case .updatedRoomsList:
|
.receive(on: DispatchQueue.main)
|
||||||
self?.updateRoomsList()
|
.sink { [weak self] result in
|
||||||
}
|
switch result {
|
||||||
}.store(in: &cancellables)
|
case .updatedRoomsList:
|
||||||
|
self?.updateRoomsList()
|
||||||
|
}
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
updateRoomsList()
|
updateRoomsList()
|
||||||
|
|
||||||
parameters.userSession.userAvatarURL { [weak self] result in
|
Task {
|
||||||
if case let .success(avatarURL) = result {
|
if case let .success(userAvatarURL) = await parameters.userSession.loadUserAvatarURL() {
|
||||||
self?.parameters.mediaProvider.loadImageFromURL(avatarURL) { result in
|
if case let .success(avatar) = await parameters.mediaProvider.loadImageFromURL(userAvatarURL) {
|
||||||
if case let .success(avatar) = result {
|
await MainActor.run {
|
||||||
self?.viewModel.updateWithUserAvatar(avatar)
|
self.viewModel.updateWithUserAvatar(avatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if case let .success(userDisplayName) = await parameters.userSession.loadUserDisplayName() {
|
||||||
parameters.userSession.userDisplayName { [weak self] result in
|
await MainActor.run {
|
||||||
if case let .success(displayName) = result {
|
self.viewModel.updateWithUserDisplayName(userDisplayName)
|
||||||
self?.viewModel.updateWithUserDisplayName(displayName)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,19 +65,21 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
roomUpdateListeners.removeAll()
|
roomUpdateListeners.removeAll()
|
||||||
|
|
||||||
roomList.forEach({ roomSummary in
|
roomList.forEach({ roomSummary in
|
||||||
roomSummary.callbacks.sink { [weak self] callback in
|
roomSummary.callbacks
|
||||||
guard let self = self else {
|
.receive(on: DispatchQueue.main)
|
||||||
return
|
.sink { [weak self] callback in
|
||||||
}
|
guard let self = self else {
|
||||||
|
return
|
||||||
switch callback {
|
}
|
||||||
case .updatedData:
|
|
||||||
if let index = self.state.rooms.firstIndex(where: { $0.id == roomSummary.id }) {
|
switch callback {
|
||||||
self.state.rooms[index] = self.buildOrUpdateRoomFromSummary(roomSummary)
|
case .updatedData:
|
||||||
|
if let index = self.state.rooms.firstIndex(where: { $0.id == roomSummary.id }) {
|
||||||
|
self.state.rooms[index] = self.buildOrUpdateRoomFromSummary(roomSummary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.store(in: &roomUpdateListeners)
|
||||||
.store(in: &roomUpdateListeners)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -98,7 +100,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
roomSummary.loadData()
|
Task {
|
||||||
|
await roomSummary.loadDetails()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom {
|
private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ struct RoomCell: View {
|
|||||||
}
|
}
|
||||||
.animation(.easeInOut, value: room)
|
.animation(.easeInOut, value: room)
|
||||||
.frame(minHeight: 60.0)
|
.frame(minHeight: 60.0)
|
||||||
.onAppear {
|
.task {
|
||||||
context.send(viewAction: .loadRoomData(roomIdentifier: room.id))
|
context.send(viewAction: .loadRoomData(roomIdentifier: room.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
|
|
||||||
super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "Unknown room 💥", bindings: RoomScreenViewStateBindings(composerText: "")))
|
super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "Unknown room 💥", bindings: RoomScreenViewStateBindings(composerText: "")))
|
||||||
|
|
||||||
timelineController.callbacks.sink { [weak self] callback in
|
timelineController.callbacks
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] callback in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
switch callback {
|
switch callback {
|
||||||
@@ -62,25 +64,37 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: RoomScreenViewAction) {
|
override func process(viewAction: RoomScreenViewAction) {
|
||||||
switch viewAction {
|
Task {
|
||||||
case .loadPreviousPage:
|
switch viewAction {
|
||||||
state.isBackPaginating = true
|
case .loadPreviousPage:
|
||||||
timelineController.paginateBackwards(Constants.backPaginationPageSize) { [weak self] _ in
|
await MainActor.run {
|
||||||
self?.state.isBackPaginating = false
|
state.isBackPaginating = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) {
|
||||||
|
default:
|
||||||
|
await MainActor.run {
|
||||||
|
state.isBackPaginating = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .itemAppeared(let id):
|
||||||
|
await timelineController.processItemAppearance(id)
|
||||||
|
case .itemDisappeared(let id):
|
||||||
|
await timelineController.processItemDisappearance(id)
|
||||||
|
case .linkClicked(let url):
|
||||||
|
MXLog.warning("Link clicked: \(url)")
|
||||||
|
case .sendMessage:
|
||||||
|
guard state.bindings.composerText.count > 0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await timelineController.sendMessage(state.bindings.composerText)
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
state.bindings.composerText = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .itemAppeared(let id):
|
|
||||||
timelineController.processItemAppearance(id)
|
|
||||||
case .itemDisappeared(let id):
|
|
||||||
timelineController.processItemDisappearance(id)
|
|
||||||
case .linkClicked(let url):
|
|
||||||
MXLog.warning("Link clicked: \(url)")
|
|
||||||
case .sendMessage:
|
|
||||||
guard state.bindings.composerText.count > 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
timelineController.sendMessage(state.bindings.composerText)
|
|
||||||
state.bindings.composerText = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,16 +28,13 @@ private class WeakUserSessionWrapper: ClientDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func didReceiveSyncUpdate() {
|
func didReceiveSyncUpdate() {
|
||||||
DispatchQueue.main.async {
|
self.userSession?.didReceiveSyncUpdate()
|
||||||
self.userSession?.didReceiveSyncUpdate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserSession: ClientDelegate {
|
class UserSession: ClientDelegate {
|
||||||
|
|
||||||
private let client: Client
|
private let client: Client
|
||||||
private let processingQueue: DispatchQueue
|
|
||||||
|
|
||||||
private(set) var rooms: [RoomProxy] = [] {
|
private(set) var rooms: [RoomProxy] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
@@ -55,7 +52,6 @@ class UserSession: ClientDelegate {
|
|||||||
|
|
||||||
init(client: Client) {
|
init(client: Client) {
|
||||||
self.client = client
|
self.client = client
|
||||||
self.processingQueue = DispatchQueue(label: "UserSessionProcessingQueue")
|
|
||||||
self.mediaProvider = MediaProvider(client: client, imageCache: ImageCache.default)
|
self.mediaProvider = MediaProvider(client: client, imageCache: ImageCache.default)
|
||||||
|
|
||||||
client.setDelegate(delegate: WeakUserSessionWrapper(userSession: self))
|
client.setDelegate(delegate: WeakUserSessionWrapper(userSession: self))
|
||||||
@@ -63,7 +59,9 @@ class UserSession: ClientDelegate {
|
|||||||
Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.")
|
Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.")
|
||||||
client.startSync()
|
client.startSync()
|
||||||
|
|
||||||
updateRooms()
|
Task {
|
||||||
|
await updateRooms()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var userIdentifier: String {
|
var userIdentifier: String {
|
||||||
@@ -75,34 +73,25 @@ class UserSession: ClientDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userDisplayName(_ completion: @escaping (Result<String, UserSessionError>) -> Void) {
|
func loadUserDisplayName() async -> Result<String, UserSessionError> {
|
||||||
processingQueue.async {
|
await withCheckedContinuation { continuation in
|
||||||
do {
|
do {
|
||||||
let displayName = try self.client.displayName()
|
let displayName = try self.client.displayName()
|
||||||
|
continuation.resume(returning: .success(displayName))
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.success(displayName))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.failedRetrievingDisplayName))
|
||||||
completion(.failure(UserSessionError.failedRetrievingDisplayName))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userAvatarURL(_ completion: @escaping (Result<String, UserSessionError>) -> Void) {
|
func loadUserAvatarURL() async -> Result<String, UserSessionError> {
|
||||||
processingQueue.async {
|
await withCheckedContinuation { continuation in
|
||||||
do {
|
do {
|
||||||
let displayName = try self.client.avatarUrl()
|
let avatarURL = try self.client.avatarUrl()
|
||||||
|
continuation.resume(returning: .success(avatarURL))
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.success(displayName))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.failedRetrievingDisplayName))
|
||||||
completion(.failure(UserSessionError.failedRetrievingDisplayName))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,43 +100,38 @@ class UserSession: ClientDelegate {
|
|||||||
|
|
||||||
func didReceiveSyncUpdate() {
|
func didReceiveSyncUpdate() {
|
||||||
Benchmark.logElapsedDurationForIdentifier("ClientSync", message: "Received sync update")
|
Benchmark.logElapsedDurationForIdentifier("ClientSync", message: "Received sync update")
|
||||||
updateRooms()
|
|
||||||
|
Task {
|
||||||
|
await updateRooms()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
func updateRooms() {
|
private func updateRooms() async {
|
||||||
var currentRooms = self.rooms
|
var currentRooms = self.rooms
|
||||||
self.processingQueue.async { [weak self] in
|
Benchmark.startTrackingForIdentifier("ClientRooms", message: "Fetching available rooms")
|
||||||
guard let self = self else {
|
let sdkRooms = self.client.rooms()
|
||||||
return
|
Benchmark.endTrackingForIdentifier("ClientRooms", message: "Retrieved \(sdkRooms.count) rooms")
|
||||||
}
|
|
||||||
|
Benchmark.startTrackingForIdentifier("ProcessingRooms", message: "Started processing \(sdkRooms.count) rooms")
|
||||||
Benchmark.startTrackingForIdentifier("ClientRooms", message: "Fetching available rooms")
|
let diff = sdkRooms.map({ $0.id()}).difference(from: currentRooms.map({ $0.id }))
|
||||||
let sdkRooms = self.client.rooms()
|
|
||||||
Benchmark.endTrackingForIdentifier("ClientRooms", message: "Retrieved \(sdkRooms.count) rooms")
|
for change in diff {
|
||||||
|
switch change {
|
||||||
Benchmark.startTrackingForIdentifier("ProcessingRooms", message: "Started processing \(sdkRooms.count) rooms")
|
case .insert(_, let id, _):
|
||||||
let diff = sdkRooms.map({ $0.id()}).difference(from: currentRooms.map({ $0.id }))
|
guard let sdkRoom = sdkRooms.first(where: { $0.id() == id }) else {
|
||||||
|
MXLog.error("Failed retrieving sdk room with id: \(id)")
|
||||||
for change in diff {
|
break
|
||||||
switch change {
|
|
||||||
case .insert(_, let id, _):
|
|
||||||
guard let sdkRoom = sdkRooms.first(where: { $0.id() == id }) else {
|
|
||||||
MXLog.error("Failed retrieving sdk room with id: \(id)")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
currentRooms.append(RoomProxy(room: sdkRoom, messageFactory: RoomMessageFactory()))
|
|
||||||
case .remove(_, let id, _):
|
|
||||||
currentRooms.removeAll { $0.id == id }
|
|
||||||
}
|
}
|
||||||
}
|
currentRooms.append(RoomProxy(room: sdkRoom, messageFactory: RoomMessageFactory()))
|
||||||
|
case .remove(_, let id, _):
|
||||||
Benchmark.endTrackingForIdentifier("ProcessingRooms", message: "Finished processing \(sdkRooms.count) rooms")
|
currentRooms.removeAll { $0.id == id }
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.rooms = currentRooms
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Benchmark.endTrackingForIdentifier("ProcessingRooms", message: "Finished processing \(sdkRooms.count) rooms")
|
||||||
|
|
||||||
|
self.rooms = currentRooms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,46 +29,6 @@ struct MediaProvider: MediaProviderProtocol {
|
|||||||
return imageCache.retrieveImageInMemoryCache(forKey: source.underlyingSource.url(), options: nil)
|
return imageCache.retrieveImageInMemoryCache(forKey: source.underlyingSource.url(), options: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImageFromSource(_ source: MediaSource, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void) {
|
|
||||||
if let image = imageFromSource(source) {
|
|
||||||
completion(.success(image))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
imageCache.retrieveImage(forKey: source.underlyingSource.url()) { result in
|
|
||||||
if case let .success(cacheResult) = result,
|
|
||||||
let image = cacheResult.image {
|
|
||||||
completion(.success(image))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
processingQueue.async {
|
|
||||||
do {
|
|
||||||
let imageData = try client.getMediaContent(source: source.underlyingSource)
|
|
||||||
|
|
||||||
guard let image = UIImage(data: Data(bytes: imageData, count: imageData.count)) else {
|
|
||||||
MXLog.error("Invalid image data")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.failure(.invalidImageData))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
imageCache.store(image, forKey: source.underlyingSource.url())
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.success(image))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed retrieving image with error: \(error)")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.failure(.failedRetrievingImage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func imageFromURL(_ url: String?) -> UIImage? {
|
func imageFromURL(_ url: String?) -> UIImage? {
|
||||||
guard let url = url else {
|
guard let url = url else {
|
||||||
return nil
|
return nil
|
||||||
@@ -77,7 +37,42 @@ struct MediaProvider: MediaProviderProtocol {
|
|||||||
return imageFromSource(MediaSource(source: mediaSourceFromUrl(url: url)))
|
return imageFromSource(MediaSource(source: mediaSourceFromUrl(url: url)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImageFromURL(_ url: String, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void) {
|
func loadImageFromURL(_ url: String) async -> Result<UIImage, MediaProviderError> {
|
||||||
return loadImageFromSource(MediaSource(source: mediaSourceFromUrl(url: url)), completion)
|
await loadImageFromSource(MediaSource(source: mediaSourceFromUrl(url: url)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImageFromSource(_ source: MediaSource) async -> Result<UIImage, MediaProviderError> {
|
||||||
|
if let image = imageFromSource(source) {
|
||||||
|
return .success(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
imageCache.retrieveImage(forKey: source.underlyingSource.url()) { result in
|
||||||
|
if case let .success(cacheResult) = result,
|
||||||
|
let image = cacheResult.image {
|
||||||
|
continuation.resume(returning: .success(image))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processingQueue.async {
|
||||||
|
do {
|
||||||
|
let imageData = try client.getMediaContent(source: source.underlyingSource)
|
||||||
|
|
||||||
|
guard let image = UIImage(data: Data(bytes: imageData, count: imageData.count)) else {
|
||||||
|
MXLog.error("Invalid image data")
|
||||||
|
continuation.resume(returning: .failure(.invalidImageData))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCache.store(image, forKey: source.underlyingSource.url())
|
||||||
|
|
||||||
|
continuation.resume(returning: .success(image))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrieving image with error: \(error)")
|
||||||
|
continuation.resume(returning: .failure(.failedRetrievingImage))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ enum MediaProviderError: Error {
|
|||||||
protocol MediaProviderProtocol {
|
protocol MediaProviderProtocol {
|
||||||
func imageFromSource(_ source: MediaSource?) -> UIImage?
|
func imageFromSource(_ source: MediaSource?) -> UIImage?
|
||||||
|
|
||||||
func loadImageFromSource(_ source: MediaSource, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void)
|
func loadImageFromSource(_ source: MediaSource) async -> Result<UIImage, MediaProviderError>
|
||||||
|
|
||||||
func imageFromURL(_ url: String?) -> UIImage?
|
func imageFromURL(_ url: String?) -> UIImage?
|
||||||
|
|
||||||
func loadImageFromURL(_ url: String, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void)
|
func loadImageFromURL(_ url: String) async -> Result<UIImage, MediaProviderError>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ struct MockMediaProvider: MediaProviderProtocol {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImageFromSource(_ source: MediaSource, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void) {
|
func loadImageFromSource(_ source: MediaSource) async -> Result<UIImage, MediaProviderError> {
|
||||||
|
return .failure(.failedRetrievingImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageFromURL(_ url: String?) -> UIImage? {
|
func imageFromURL(_ url: String?) -> UIImage? {
|
||||||
@@ -30,4 +30,8 @@ struct MockMediaProvider: MediaProviderProtocol {
|
|||||||
func loadImageFromURL(_ url: String, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void) {
|
func loadImageFromURL(_ url: String, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadImageFromURL(_ url: String) async -> Result<UIImage, MediaProviderError> {
|
||||||
|
return .failure(.failedRetrievingImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class MemberDetailProvider: MemberDetailProviderProtocol {
|
class MemberDetailProvider: MemberDetailProviderProtocol {
|
||||||
private let roomProxy: RoomProxyProtocol?
|
private let roomProxy: RoomProxyProtocol
|
||||||
private var memberAvatars = [String: String]()
|
private var memberAvatars = [String: String]()
|
||||||
private var memberDisplayNames = [String: String]()
|
private var memberDisplayNames = [String: String]()
|
||||||
|
|
||||||
@@ -18,58 +18,38 @@ class MemberDetailProvider: MemberDetailProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func avatarURLForUserId(_ userId: String) -> String? {
|
func avatarURLForUserId(_ userId: String) -> String? {
|
||||||
self.memberAvatars[userId]
|
memberAvatars[userId]
|
||||||
}
|
}
|
||||||
|
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void) {
|
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, MemberDetailProviderError> {
|
||||||
guard let roomProxy = roomProxy else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let avatarURL = avatarURLForUserId(userId) {
|
if let avatarURL = avatarURLForUserId(userId) {
|
||||||
completion(.success(avatarURL))
|
return .success(avatarURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomProxy.avatarURLForUserId(userId, completion: { [weak self] result in
|
switch await roomProxy.loadAvatarURLForUserId(userId) {
|
||||||
guard let self = self else {
|
case .success(let avatarURL):
|
||||||
return
|
memberAvatars[userId] = avatarURL
|
||||||
}
|
return .success(avatarURL)
|
||||||
|
case .failure:
|
||||||
switch result {
|
return .failure(.failedRetrievingUserAvatarURL)
|
||||||
case .success(let avatarURL):
|
}
|
||||||
self.memberAvatars[userId] = avatarURL
|
|
||||||
completion(.success(avatarURL))
|
|
||||||
case .failure:
|
|
||||||
completion(.failure(.failedRetrievingUserAvatarURL))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayNameForUserId(_ userId: String) -> String? {
|
func displayNameForUserId(_ userId: String) -> String? {
|
||||||
self.memberDisplayNames[userId]
|
memberDisplayNames[userId]
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void) {
|
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, MemberDetailProviderError> {
|
||||||
guard let roomProxy = roomProxy else {
|
if let displayName = displayNameForUserId(userId) {
|
||||||
return
|
return .success(displayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let avatarURL = displayNameForUserId(userId) {
|
switch await roomProxy.loadDisplayNameForUserId(userId) {
|
||||||
completion(.success(avatarURL))
|
case .success(let displayName):
|
||||||
|
memberDisplayNames[userId] = displayName
|
||||||
|
return .success(displayName)
|
||||||
|
case .failure:
|
||||||
|
return .failure(.failedRetrievingUserDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomProxy.displayNameForUserId(userId, completion: { [weak self] result in
|
|
||||||
guard let self = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case .success(let displayName):
|
|
||||||
self.memberDisplayNames[userId] = displayName
|
|
||||||
completion(.success(displayName))
|
|
||||||
case .failure:
|
|
||||||
completion(.failure(.failedRetrievingUserDisplayName))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ enum MemberDetailProviderError: Error {
|
|||||||
|
|
||||||
protocol MemberDetailProviderProtocol {
|
protocol MemberDetailProviderProtocol {
|
||||||
func avatarURLForUserId(_ userId: String) -> String?
|
func avatarURLForUserId(_ userId: String) -> String?
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void)
|
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, MemberDetailProviderError>
|
||||||
|
|
||||||
func displayNameForUserId(_ userId: String) -> String?
|
func displayNameForUserId(_ userId: String) -> String?
|
||||||
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailProviderError>) -> Void)
|
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, MemberDetailProviderError>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,27 +27,27 @@ struct MockRoomProxy: RoomProxyProtocol {
|
|||||||
|
|
||||||
var callbacks = PassthroughSubject<RoomProxyCallback, Never>()
|
var callbacks = PassthroughSubject<RoomProxyCallback, Never>()
|
||||||
|
|
||||||
func displayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void) {
|
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||||
completion(.success("Room display name"))
|
return .failure(.failedRetrievingMemberDisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||||
|
return .failure(.failedRetrievingMemberAvatarURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDisplayName() async -> Result<String, RoomProxyError> {
|
||||||
|
return .failure(.failedRetrievingDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startLiveEventListener() {
|
func startLiveEventListener() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(count: UInt, callback: ((Result<Void, RoomProxyError>) -> Void)?) {
|
func paginateBackwards(count: UInt) async -> Result<Void, RoomProxyError> {
|
||||||
|
return .failure(.backwardStreamNotAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendMessage(_ message: String, callback: ((Result<Void, RoomProxyError>) -> Void)?) {
|
|
||||||
|
|
||||||
|
func sendMessage(_ message: String) async -> Result<Void, RoomProxyError> {
|
||||||
|
return .failure(.failedSendingMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ private class WeakRoomProxyWrapper: RoomDelegate {
|
|||||||
// MARK: - RoomDelegate
|
// MARK: - RoomDelegate
|
||||||
|
|
||||||
func didReceiveMessage(message: AnyMessage) {
|
func didReceiveMessage(message: AnyMessage) {
|
||||||
DispatchQueue.main.async {
|
self.roomProxy?.appendMessage(message)
|
||||||
self.roomProxy?.appendMessage(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +29,6 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
private let room: Room
|
private let room: Room
|
||||||
private let messageFactory: RoomMessageFactory
|
private let messageFactory: RoomMessageFactory
|
||||||
|
|
||||||
private let generalProcessingQueue: DispatchQueue
|
|
||||||
private let messageProcessingQueue: DispatchQueue
|
|
||||||
|
|
||||||
private var backwardStream: BackwardsStreamProtocol?
|
private var backwardStream: BackwardsStreamProtocol?
|
||||||
|
|
||||||
private(set) var displayName: String?
|
private(set) var displayName: String?
|
||||||
@@ -45,18 +40,13 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
init(room: Room, messageFactory: RoomMessageFactory) {
|
init(room: Room, messageFactory: RoomMessageFactory) {
|
||||||
self.room = room
|
self.room = room
|
||||||
self.messageFactory = messageFactory
|
self.messageFactory = messageFactory
|
||||||
generalProcessingQueue = DispatchQueue(label: "RoomProxyGeneralProcessingQueue")
|
|
||||||
messageProcessingQueue = DispatchQueue(label: "RoomProxyMessageProcessingQueue")
|
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
messageProcessingQueue.async {
|
|
||||||
let backwardStream = room.startLiveEventListener()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.backwardStream = backwardStream
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
room.setDelegate(delegate: WeakRoomProxyWrapper(roomProxy: self))
|
room.setDelegate(delegate: WeakRoomProxyWrapper(roomProxy: self))
|
||||||
|
|
||||||
|
Task {
|
||||||
|
backwardStream = room.startLiveEventListener()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
@@ -95,66 +85,50 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
room.avatarUrl()
|
room.avatarUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||||
generalProcessingQueue.async {
|
await withCheckedContinuation({ continuation in
|
||||||
do {
|
do {
|
||||||
let avatarURL = try self.room.memberAvatarUrl(userId: userId)
|
let avatarURL = try self.room.memberAvatarUrl(userId: userId)
|
||||||
|
continuation.resume(returning: .success(avatarURL))
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.success(avatarURL))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.failedRetrievingMemberAvatarURL))
|
||||||
completion(.failure(RoomProxyError.failedRetrievingMemberAvatarURL))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||||
generalProcessingQueue.async {
|
await withCheckedContinuation({ continuation in
|
||||||
do {
|
do {
|
||||||
let displayName = try self.room.memberDisplayName(userId: userId)
|
let displayName = try self.room.memberDisplayName(userId: userId)
|
||||||
|
continuation.resume(returning: .success(displayName))
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.success(displayName))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.failedRetrievingMemberDisplayName))
|
||||||
completion(.failure(RoomProxyError.failedRetrievingMemberDisplayName))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void) {
|
|
||||||
if let displayName = displayName {
|
|
||||||
completion(.success(displayName))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
generalProcessingQueue.async {
|
func loadDisplayName() async -> Result<String, RoomProxyError> {
|
||||||
|
await withCheckedContinuation({ continuation in
|
||||||
|
if let displayName = displayName {
|
||||||
|
continuation.resume(returning: .success(displayName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let displayName = try self.room.displayName()
|
let displayName = try self.room.displayName()
|
||||||
self.displayName = displayName
|
self.displayName = displayName
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .success(displayName))
|
||||||
completion(.success(displayName))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.failedRetrievingDisplayName))
|
||||||
completion(.failure(.failedRetrievingDisplayName))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(count: UInt, callback: ((Result<Void, RoomProxyError>) -> Void)?) {
|
func paginateBackwards(count: UInt) async -> Result<Void, RoomProxyError> {
|
||||||
messageProcessingQueue.async {
|
await withCheckedContinuation { continuation in
|
||||||
guard let backwardStream = self.backwardStream else {
|
guard let backwardStream = self.backwardStream else {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.backwardStreamNotAvailable))
|
||||||
callback?(.failure(.backwardStreamNotAvailable))
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,28 +140,22 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
self.messageFactory.buildRoomMessageFrom(message)
|
self.messageFactory.buildRoomMessageFrom(message)
|
||||||
}.reversed()
|
}.reversed()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.messages.insert(contentsOf: messages, at: 0)
|
||||||
self.messages.insert(contentsOf: messages, at: 0)
|
|
||||||
callback?(.success(()))
|
continuation.resume(returning: .success(()))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(_ message: String, callback: ((Result<Void, RoomProxyError>) -> Void)?) {
|
func sendMessage(_ message: String) async -> Result<Void, RoomProxyError> {
|
||||||
let messageContent = messageEventContentFromMarkdown(md: message)
|
let messageContent = messageEventContentFromMarkdown(md: message)
|
||||||
let transactionId = genTransactionId()
|
let transactionId = genTransactionId()
|
||||||
|
|
||||||
messageProcessingQueue.async {
|
return await withCheckedContinuation { continuation in
|
||||||
do {
|
do {
|
||||||
try self.room.send(msg: messageContent, txnId: transactionId)
|
try self.room.send(msg: messageContent, txnId: transactionId)
|
||||||
|
continuation.resume(returning: .success(()))
|
||||||
DispatchQueue.main.async {
|
|
||||||
callback?(.success(()))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
continuation.resume(returning: .failure(.failedSendingMessage))
|
||||||
callback?(.failure(.failedSendingMessage))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,15 +37,15 @@ protocol RoomProxyProtocol {
|
|||||||
|
|
||||||
var avatarURL: String? { get }
|
var avatarURL: String? { get }
|
||||||
|
|
||||||
func displayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void)
|
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
|
||||||
|
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void)
|
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
|
||||||
|
|
||||||
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void)
|
func loadDisplayName() async -> Result<String, RoomProxyError>
|
||||||
|
|
||||||
func paginateBackwards(count: UInt, callback: ((Result<Void, RoomProxyError>) -> Void)?)
|
func paginateBackwards(count: UInt) async -> Result<Void, RoomProxyError>
|
||||||
|
|
||||||
func sendMessage(_ message: String, callback: ((Result<Void, RoomProxyError>) -> Void)?)
|
func sendMessage(_ message: String) async -> Result<Void, RoomProxyError>
|
||||||
|
|
||||||
var callbacks: PassthroughSubject<RoomProxyCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomProxyCallback, Never> { get }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,20 @@ struct EventBriefFactory: EventBriefFactoryProtocol {
|
|||||||
self.memberDetailProvider = memberDetailProvider
|
self.memberDetailProvider = memberDetailProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventBriefForMessage(_ message: RoomMessageProtocol?, completion: @escaping ((EventBrief?) -> Void)) {
|
func eventBriefForMessage(_ message: RoomMessageProtocol?) async -> EventBrief? {
|
||||||
guard let message = message else {
|
guard let message = message else {
|
||||||
completion(nil)
|
return nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch message {
|
switch message {
|
||||||
case is ImageRoomMessage:
|
case is ImageRoomMessage:
|
||||||
completion(nil)
|
return nil
|
||||||
case let message as TextRoomMessage:
|
case let message as TextRoomMessage:
|
||||||
buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
|
return await buildEventBrief(message: message, htmlBody: message.htmlBody)
|
||||||
case let message as NoticeRoomMessage:
|
case let message as NoticeRoomMessage:
|
||||||
buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
|
return await buildEventBrief(message: message, htmlBody: message.htmlBody)
|
||||||
case let message as EmoteRoomMessage:
|
case let message as EmoteRoomMessage:
|
||||||
buildEventBrief(message: message, htmlBody: message.htmlBody, completion: completion)
|
return await buildEventBrief(message: message, htmlBody: message.htmlBody)
|
||||||
default:
|
default:
|
||||||
fatalError("Unknown room message.")
|
fatalError("Unknown room message.")
|
||||||
}
|
}
|
||||||
@@ -38,26 +37,24 @@ struct EventBriefFactory: EventBriefFactoryProtocol {
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?, completion: @escaping ((EventBrief?) -> Void)) {
|
private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?) async -> EventBrief? {
|
||||||
memberDetailProvider.displayNameForUserId(message.sender) { result in
|
switch await memberDetailProvider.loadDisplayNameForUserId(message.sender) {
|
||||||
switch result {
|
case .success(let displayName):
|
||||||
case .success(let displayName):
|
return EventBrief(eventId: message.id,
|
||||||
completion(EventBrief(eventId: message.id,
|
senderId: message.sender,
|
||||||
senderId: message.sender,
|
senderDisplayName: displayName,
|
||||||
senderDisplayName: displayName,
|
body: message.body,
|
||||||
body: message.body,
|
htmlBody: htmlBody,
|
||||||
htmlBody: htmlBody,
|
date: message.originServerTs)
|
||||||
date: message.originServerTs))
|
case .failure(let error):
|
||||||
case .failure(let error):
|
MXLog.error("Failed fetching sender display name with error: \(error)")
|
||||||
MXLog.error("Failed fetching sender display name with error: \(error)")
|
|
||||||
|
return EventBrief(eventId: message.id,
|
||||||
completion(EventBrief(eventId: message.id,
|
senderId: message.sender,
|
||||||
senderId: message.sender,
|
senderDisplayName: nil,
|
||||||
senderDisplayName: nil,
|
body: message.body,
|
||||||
body: message.body,
|
htmlBody: htmlBody,
|
||||||
htmlBody: htmlBody,
|
date: message.originServerTs)
|
||||||
date: message.originServerTs))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol EventBriefFactoryProtocol {
|
protocol EventBriefFactoryProtocol {
|
||||||
func eventBriefForMessage(_ message: RoomMessageProtocol?, completion: @escaping ((EventBrief?) -> Void))
|
func eventBriefForMessage(_ message: RoomMessageProtocol?) async -> EventBrief?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ struct MockRoomSummary: RoomSummaryProtocol {
|
|||||||
|
|
||||||
var avatar: UIImage?
|
var avatar: UIImage?
|
||||||
|
|
||||||
func loadData() {
|
func loadDetails() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,81 +71,81 @@ class RoomSummary: RoomSummaryProtocol {
|
|||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
self.eventBriefFactory = eventBriefFactory
|
self.eventBriefFactory = eventBriefFactory
|
||||||
|
|
||||||
eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) { [weak self] result in
|
Task {
|
||||||
self?.lastMessage = result
|
lastMessage = await eventBriefFactory.eventBriefForMessage(roomProxy.messages.last)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomProxy.callbacks.sink { [weak self] callback in
|
roomProxy.callbacks
|
||||||
guard let self = self else {
|
.receive(on: DispatchQueue.main)
|
||||||
return
|
.sink { [weak self] callback in
|
||||||
}
|
guard let self = self else {
|
||||||
|
return
|
||||||
switch callback {
|
}
|
||||||
case .updatedMessages:
|
|
||||||
self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) { [weak self] result in
|
switch callback {
|
||||||
self?.lastMessage = result
|
case .updatedMessages:
|
||||||
|
Task {
|
||||||
|
self.lastMessage = await eventBriefFactory.eventBriefForMessage(roomProxy.messages.last)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.store(in: &roomUpdateListeners)
|
||||||
.store(in: &roomUpdateListeners)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadData() {
|
func loadDetails() async {
|
||||||
if hasLoadedData {
|
if hasLoadedData {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDisplayName()
|
await withTaskGroup(of: Void.self) { group in
|
||||||
loadLastMessage()
|
group.addTask {
|
||||||
loadAvatar()
|
await self.loadDisplayName()
|
||||||
|
}
|
||||||
|
group.addTask {
|
||||||
|
await self.loadAvatar()
|
||||||
|
}
|
||||||
|
group.addTask {
|
||||||
|
await self.loadLastMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasLoadedData = true
|
hasLoadedData = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func loadDisplayName() {
|
private func loadDisplayName() async {
|
||||||
roomProxy.displayName { [weak self] result in
|
switch await roomProxy.loadDisplayName() {
|
||||||
guard let self = self else { return }
|
case .success(let displayName):
|
||||||
|
self.displayName = displayName
|
||||||
switch result {
|
case .failure(let error):
|
||||||
case .success(let displayName):
|
MXLog.error("Failed fetching room display name with error: \(error)")
|
||||||
self.displayName = displayName
|
|
||||||
case .failure(let error):
|
|
||||||
MXLog.error("Failed fetching room display name with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadLastMessage() {
|
private func loadAvatar() async {
|
||||||
if roomProxy.messages.last == nil {
|
guard let avatarURL = roomProxy.avatarURL else {
|
||||||
roomProxy.paginateBackwards(count: 1) { [weak self] result in
|
return
|
||||||
guard let self = self else { return }
|
}
|
||||||
|
|
||||||
switch result {
|
switch await mediaProvider.loadImageFromURL(avatarURL) {
|
||||||
case .success:
|
case .success(let avatar):
|
||||||
self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last) { [weak self] result in
|
self.avatar = avatar
|
||||||
self?.lastMessage = result
|
case .failure(let error):
|
||||||
}
|
MXLog.error("Failed fetching room avatar with error: \(error)")
|
||||||
case .failure(let error):
|
|
||||||
MXLog.error("Failed back paginating with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadAvatar() {
|
private func loadLastMessage() async {
|
||||||
if let avatarURL = roomProxy.avatarURL {
|
guard roomProxy.messages.last == nil else {
|
||||||
mediaProvider.loadImageFromURL(avatarURL) { [weak self] result in
|
return
|
||||||
guard let self = self else { return }
|
}
|
||||||
|
|
||||||
switch result {
|
switch await roomProxy.paginateBackwards(count: 1) {
|
||||||
case .success(let image):
|
case .success:
|
||||||
self.avatar = image
|
self.lastMessage = await self.eventBriefFactory.eventBriefForMessage(self.roomProxy.messages.last)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Failed fetching room avatar with error: \(error)")
|
MXLog.error("Failed back paginating with error: \(error)")
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ protocol RoomSummaryProtocol {
|
|||||||
|
|
||||||
var callbacks: PassthroughSubject<RoomSummaryCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomSummaryCallback, Never> { get }
|
||||||
|
|
||||||
func loadData()
|
func loadDetails() async
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"),
|
SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString, text: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, senderId: "Bob")]
|
TextRoomTimelineItem(id: UUID().uuidString, text: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, senderId: "Bob")]
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineControllerError>) -> Void)) {
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||||
callbacks.send(.updatedTimelineItems)
|
return .failure(.generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processItemAppearance(_ itemId: String) {
|
func processItemAppearance(_ itemId: String) async {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processItemDisappearance(_ itemId: String) {
|
func processItemDisappearance(_ itemId: String) async {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(_ message: String) {
|
func sendMessage(_ message: String) async {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,31 +45,29 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt, callback: @escaping ((Result<Void, RoomTimelineControllerError>) -> Void)) {
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError> {
|
||||||
timelineProvider.paginateBackwards(count) { [weak self] result in
|
switch await timelineProvider.paginateBackwards(count) {
|
||||||
switch result {
|
case .success:
|
||||||
case .success:
|
updateTimelineItems()
|
||||||
callback(.success(()))
|
return .success(())
|
||||||
self?.updateTimelineItems()
|
case .failure:
|
||||||
case .failure:
|
return .failure(.generic)
|
||||||
callback(.failure(.generic))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processItemAppearance(_ itemId: String) {
|
func processItemAppearance(_ itemId: String) async {
|
||||||
guard let timelineItem = self.timelineItems.filter({ $0.id == itemId}).first else {
|
guard let timelineItem = self.timelineItems.filter({ $0.id == itemId}).first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = timelineItem as? EventBasedTimelineItemProtocol {
|
if let item = timelineItem as? EventBasedTimelineItemProtocol {
|
||||||
loadUserAvatarForTimelineItem(item)
|
await loadUserAvatarForTimelineItem(item)
|
||||||
loadUserDisplayNameForTimelineItem(item)
|
await loadUserDisplayNameForTimelineItem(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch timelineItem {
|
switch timelineItem {
|
||||||
case let item as ImageRoomTimelineItem:
|
case let item as ImageRoomTimelineItem:
|
||||||
loadImageForTimelineItem(item)
|
await loadImageForTimelineItem(item)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -79,8 +77,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(_ message: String) {
|
func sendMessage(_ message: String) async {
|
||||||
timelineProvider.sendMessage(message)
|
switch await timelineProvider.sendMessage(message) {
|
||||||
|
case .success:
|
||||||
|
break
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
@@ -124,7 +127,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
return Calendar.current.isDate(lhs.originServerTs, inSameDayAs: rhs.originServerTs)
|
return Calendar.current.isDate(lhs.originServerTs, inSameDayAs: rhs.originServerTs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) {
|
private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) async {
|
||||||
if timelineItem.image != nil {
|
if timelineItem.image != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -133,65 +136,69 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaProvider.loadImageFromSource(source) { [weak self] result in
|
switch await mediaProvider.loadImageFromSource(source) {
|
||||||
guard let self = self else {
|
case .success(let image):
|
||||||
|
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
||||||
|
var item = self.timelineItems[index] as? ImageRoomTimelineItem else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .success(image) = result {
|
item.image = image
|
||||||
|
self.timelineItems[index] = item
|
||||||
|
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async {
|
||||||
|
if timelineItem.shouldShowSenderDetails == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await memberDetailProvider.loadAvatarURLForUserId(timelineItem.senderId) {
|
||||||
|
case .success(let avatarURL):
|
||||||
|
guard let avatarURL = avatarURL else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch await mediaProvider.loadImageFromURL(avatarURL) {
|
||||||
|
case .success(let avatar):
|
||||||
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
||||||
var item = self.timelineItems[index] as? ImageRoomTimelineItem else {
|
var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
item.image = image
|
item.senderAvatar = avatar
|
||||||
self.timelineItems[index] = item
|
self.timelineItems[index] = item
|
||||||
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) {
|
private func loadUserDisplayNameForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async {
|
||||||
if timelineItem.shouldShowSenderDetails == false {
|
if timelineItem.shouldShowSenderDetails == false {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memberDetailProvider.avatarURLForUserId(timelineItem.senderId) { result in
|
switch await memberDetailProvider.loadDisplayNameForUserId(timelineItem.senderId) {
|
||||||
if case let .success(avatarURL) = result,
|
case .success(let displayName):
|
||||||
let avatarURL = avatarURL {
|
guard let displayName = displayName,
|
||||||
self.mediaProvider.loadImageFromURL(avatarURL) { result in
|
let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
||||||
if case let .success(image) = result {
|
var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else {
|
||||||
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
return
|
||||||
var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.senderAvatar = image
|
|
||||||
self.timelineItems[index] = item
|
|
||||||
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadUserDisplayNameForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) {
|
|
||||||
if timelineItem.shouldShowSenderDetails == false {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
memberDetailProvider.displayNameForUserId(timelineItem.senderId) { result in
|
|
||||||
if case let .success(displayName) = result,
|
|
||||||
let displayName = displayName {
|
|
||||||
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
|
||||||
var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.senderDisplayName = displayName
|
|
||||||
self.timelineItems[index] = item
|
|
||||||
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.senderDisplayName = displayName
|
||||||
|
self.timelineItems[index] = item
|
||||||
|
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ enum RoomTimelineControllerError: Error {
|
|||||||
protocol RoomTimelineControllerProtocol {
|
protocol RoomTimelineControllerProtocol {
|
||||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt, callback: @escaping ((Result<Void, RoomTimelineControllerError>) -> Void))
|
|
||||||
|
|
||||||
func processItemAppearance(_ itemId: String)
|
func processItemAppearance(_ itemId: String) async
|
||||||
|
|
||||||
func processItemDisappearance(_ itemId: String)
|
func processItemDisappearance(_ itemId: String) async
|
||||||
|
|
||||||
func sendMessage(_ message: String)
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||||
|
|
||||||
|
func sendMessage(_ message: String) async
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
private let roomProxy: RoomProxyProtocol
|
private let roomProxy: RoomProxyProtocol
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
let callbacks = PassthroughSubject<RoomTimelineCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineProviderCallback, Never>()
|
||||||
|
|
||||||
init(roomProxy: RoomProxyProtocol) {
|
init(roomProxy: RoomProxyProtocol) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
@@ -32,25 +32,21 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
|||||||
roomProxy.messages
|
roomProxy.messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineError>) -> Void)?) {
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError> {
|
||||||
self.roomProxy.paginateBackwards(count: count) { result in
|
switch await roomProxy.paginateBackwards(count: count) {
|
||||||
switch result {
|
case .success:
|
||||||
case .success:
|
return .success(())
|
||||||
callback?(.success(()))
|
case .failure:
|
||||||
case .failure:
|
return .failure(.generic)
|
||||||
callback?(.failure(.generic))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessage(_ message: String) {
|
func sendMessage(_ message: String) async -> Result<Void, RoomTimelineProviderError> {
|
||||||
roomProxy.sendMessage(message) { result in
|
switch await roomProxy.sendMessage(message) {
|
||||||
switch result {
|
case .success:
|
||||||
case .success:
|
return .success(())
|
||||||
break
|
case .failure:
|
||||||
case .failure(let error):
|
return .failure(.failedSendingMessage)
|
||||||
MXLog.error("Failed sending message with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,20 +9,21 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
enum RoomTimelineCallback {
|
enum RoomTimelineProviderCallback {
|
||||||
case updatedMessages
|
case updatedMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomTimelineError: Error {
|
enum RoomTimelineProviderError: Error {
|
||||||
|
case failedSendingMessage
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol RoomTimelineProviderProtocol {
|
protocol RoomTimelineProviderProtocol {
|
||||||
var callbacks: PassthroughSubject<RoomTimelineCallback, Never> { get }
|
var callbacks: PassthroughSubject<RoomTimelineProviderCallback, Never> { get }
|
||||||
|
|
||||||
var messages: [RoomMessageProtocol] { get }
|
var messages: [RoomMessageProtocol] { get }
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineError>) -> Void)?)
|
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineProviderError>
|
||||||
|
|
||||||
func sendMessage(_ message: String)
|
func sendMessage(_ message: String) async -> Result<Void, RoomTimelineProviderError>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user