mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Automatically try reloading failed images on network changes (#3170)
* Add the networkMonitor to the NSE and delete the old MockMediaLoader * Generate a MediaLoaderMock through AutoMockable and use it in the MediaProviderTests * Implement an ImageProvider mechanism that automatically tries reloading images when connectivity is established again and use it for LoadableImages * Merge the ImageProvider protocol back into the MediaProvider * Address PR review comments * Provide a default sdk client request configuration * Address tasks not automatically cancelling themselves when views get deallocated
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ImageProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSourceProxy?, size: CGSize?) -> UIImage?
|
||||
|
||||
func loadImageFromSource(_ source: MediaSourceProxy, size: CGSize?) async -> Result<UIImage, MediaProviderError>
|
||||
|
||||
func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result<Data, MediaProviderError>
|
||||
}
|
||||
|
||||
extension ImageProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSourceProxy?) -> UIImage? {
|
||||
imageFromSource(source, size: nil)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol MediaLoaderProtocol {
|
||||
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data
|
||||
|
||||
|
||||
@@ -14,17 +14,21 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Kingfisher
|
||||
import UIKit
|
||||
|
||||
struct MediaProvider: MediaProviderProtocol {
|
||||
private let mediaLoader: MediaLoaderProtocol
|
||||
private let imageCache: Kingfisher.ImageCache
|
||||
private let networkMonitor: NetworkMonitorProtocol?
|
||||
|
||||
init(mediaLoader: MediaLoaderProtocol,
|
||||
imageCache: Kingfisher.ImageCache) {
|
||||
imageCache: Kingfisher.ImageCache,
|
||||
networkMonitor: NetworkMonitorProtocol?) {
|
||||
self.mediaLoader = mediaLoader
|
||||
self.imageCache = imageCache
|
||||
self.networkMonitor = networkMonitor
|
||||
}
|
||||
|
||||
// MARK: Images
|
||||
@@ -71,6 +75,45 @@ struct MediaProvider: MediaProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task<UIImage, any Error> {
|
||||
guard let networkMonitor else {
|
||||
fatalError("This method shouldn't be invoked without a NetworkMonitor set.")
|
||||
}
|
||||
|
||||
return Task {
|
||||
if case let .success(image) = await loadImageFromSource(source, size: size) {
|
||||
return image
|
||||
}
|
||||
|
||||
guard !Task.isCancelled else {
|
||||
throw MediaProviderError.cancelled
|
||||
}
|
||||
|
||||
for await reachability in networkMonitor.reachabilityPublisher.values {
|
||||
guard !Task.isCancelled else {
|
||||
throw MediaProviderError.cancelled
|
||||
}
|
||||
|
||||
guard reachability == .reachable else {
|
||||
continue
|
||||
}
|
||||
|
||||
switch await loadImageFromSource(source, size: size) {
|
||||
case .success(let image):
|
||||
return image
|
||||
case .failure:
|
||||
// If it fails after a retry with the network available
|
||||
// then something else must be wrong. Bail out.
|
||||
if reachability == .reachable {
|
||||
throw MediaProviderError.cancelled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw MediaProviderError.cancelled
|
||||
}
|
||||
}
|
||||
|
||||
func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result<Data, MediaProviderError> {
|
||||
do {
|
||||
let imageData = try await mediaLoader.loadMediaContentForSource(source)
|
||||
|
||||
@@ -22,14 +22,29 @@ enum MediaProviderError: Error {
|
||||
case failedRetrievingFile
|
||||
case invalidImageData
|
||||
case failedRetrievingThumbnail
|
||||
case cancelled
|
||||
}
|
||||
|
||||
protocol MediaProviderProtocol: ImageProviderProtocol {
|
||||
func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result<MediaFileHandleProxy, MediaProviderError>
|
||||
protocol MediaProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSourceProxy?, size: CGSize?) -> UIImage?
|
||||
func loadImageFromSource(_ source: MediaSourceProxy, size: CGSize?) async -> Result<UIImage, MediaProviderError>
|
||||
func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result<Data, MediaProviderError>
|
||||
func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task<UIImage, Error>
|
||||
|
||||
func loadThumbnailForSource(source: MediaSourceProxy, size: CGSize) async -> Result<Data, MediaProviderError>
|
||||
|
||||
func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result<MediaFileHandleProxy, MediaProviderError>
|
||||
}
|
||||
|
||||
extension MediaProviderProtocol {
|
||||
func imageFromSource(_ source: MediaSourceProxy?) -> UIImage? {
|
||||
imageFromSource(source, size: nil)
|
||||
}
|
||||
|
||||
func loadImageRetryingOnReconnection(_ source: MediaSourceProxy) -> Task<UIImage, Error> {
|
||||
loadImageRetryingOnReconnection(source, size: nil)
|
||||
}
|
||||
|
||||
func loadFileFromSource(_ source: MediaSourceProxy) async -> Result<MediaFileHandleProxy, MediaProviderError> {
|
||||
await loadFileFromSource(source, body: nil)
|
||||
}
|
||||
|
||||
@@ -58,4 +58,14 @@ struct MockMediaProvider: MediaProviderProtocol {
|
||||
}
|
||||
return .failure(.failedRetrievingFile)
|
||||
}
|
||||
|
||||
func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task<UIImage, any Error> {
|
||||
Task {
|
||||
guard let image = UIImage(systemName: "photo") else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
|
||||
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol {
|
||||
let mediaProvider = MediaProvider(mediaLoader: clientProxy,
|
||||
imageCache: .onlyInMemory)
|
||||
imageCache: .onlyInMemory,
|
||||
networkMonitor: networkMonitor)
|
||||
|
||||
let voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user