Timeline item sender profiles (#463)

* Adopt timeline item sender profiles for displaying user avatars and display names
* Prevent images from automatically being evicted from the in-memory cache when entering background
* Get rid of all the `urlString: String`s and start using proper URLs
* Add back manual display name loading as the sender profiles aren't working properly yet
* Implement a sender profile.
* Bump the RustSDK to v1.0.32-alpha

Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
Stefan Ceriu
2023-01-18 16:29:44 +02:00
committed by GitHub
parent 8f906c7be7
commit 8f3f83167b
70 changed files with 390 additions and 495 deletions
@@ -34,36 +34,37 @@ struct MediaProvider: MediaProviderProtocol {
}
func imageFromSource(_ source: MediaSourceProxy?, avatarSize: AvatarSize?) -> UIImage? {
guard let source else {
guard let url = source?.url else {
return nil
}
let cacheKey = cacheKeyForURLString(source.url, avatarSize: avatarSize)
let cacheKey = cacheKeyForURL(url, avatarSize: avatarSize)
return imageCache.retrieveImageInMemoryCache(forKey: cacheKey, options: nil)
}
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? {
guard let urlString else {
func imageFromURL(_ url: URL?, avatarSize: AvatarSize?) -> UIImage? {
guard let url else {
return nil
}
return imageFromSource(.init(urlString: urlString), avatarSize: avatarSize)
return imageFromSource(.init(url: url), avatarSize: avatarSize)
}
func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
await loadImageFromSource(.init(urlString: urlString), avatarSize: avatarSize)
func loadImageFromURL(_ url: URL, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
await loadImageFromSource(.init(url: url), avatarSize: avatarSize)
}
func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
if let image = imageFromSource(source, avatarSize: avatarSize) {
return .success(image)
}
#warning("Media loading should check for existing in flight operations and de-dupe requests.")
let loadImageBgTask = await backgroundTaskService?.startBackgroundTask(withName: "LoadImage: \(source.url.hashValue)")
defer {
loadImageBgTask?.stop()
}
let cacheKey = cacheKeyForURLString(source.url, avatarSize: avatarSize)
let cacheKey = cacheKeyForURL(source.url, avatarSize: avatarSize)
if case let .success(cacheResult) = await imageCache.retrieveImage(forKey: cacheKey),
let image = cacheResult.image {
@@ -96,7 +97,7 @@ struct MediaProvider: MediaProviderProtocol {
guard let source else {
return nil
}
let cacheKey = fileCacheKeyForURLString(source.url)
let cacheKey = fileCacheKeyForURL(source.url)
return fileCache.file(forKey: cacheKey, fileExtension: fileExtension)
}
@@ -110,11 +111,11 @@ struct MediaProvider: MediaProviderProtocol {
loadFileBgTask?.stop()
}
let cacheKey = fileCacheKeyForURLString(source.url)
let cacheKey = fileCacheKeyForURL(source.url)
do {
let data = try await mediaProxy.loadMediaContentForSource(source)
let url = try fileCache.store(data, with: fileExtension, forKey: cacheKey)
return .success(url)
} catch {
@@ -122,34 +123,33 @@ struct MediaProvider: MediaProviderProtocol {
return .failure(.failedRetrievingImage)
}
}
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? {
guard let urlString else {
func fileFromURL(_ url: URL?, fileExtension: String) -> URL? {
guard let url else {
return nil
}
return fileFromSource(MediaSourceProxy(urlString: urlString),
fileExtension: fileExtension)
return fileFromSource(MediaSourceProxy(url: url), fileExtension: fileExtension)
}
func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError> {
await loadFileFromSource(MediaSourceProxy(urlString: urlString),
fileExtension: fileExtension)
func loadFileFromURL(_ url: URL, fileExtension: String) async -> Result<URL, MediaProviderError> {
await loadFileFromSource(MediaSourceProxy(url: url), fileExtension: fileExtension)
}
// MARK: - Private
private func cacheKeyForURLString(_ urlString: String, avatarSize: AvatarSize?) -> String {
private func cacheKeyForURL(_ url: URL, avatarSize: AvatarSize?) -> String {
if let avatarSize {
return "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
return "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}"
} else {
return urlString
return url.absoluteString
}
}
private func fileCacheKeyForURLString(_ urlString: String) -> String {
guard let component = urlString.split(separator: "/").last else {
return urlString
private func fileCacheKeyForURL(_ url: URL) -> String {
let component = url.lastPathComponent
guard !component.isEmpty else {
return url.absoluteString
}
return String(component)
}
@@ -28,17 +28,17 @@ protocol MediaProviderProtocol {
@discardableResult func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage?
func imageFromURL(_ url: URL?, avatarSize: AvatarSize?) -> UIImage?
@discardableResult func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
@discardableResult func loadImageFromURL(_ url: URL, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
func fileFromSource(_ source: MediaSourceProxy?, fileExtension: String) -> URL?
@discardableResult func loadFileFromSource(_ source: MediaSourceProxy, fileExtension: String) async -> Result<URL, MediaProviderError>
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL?
func fileFromURL(_ url: URL?, fileExtension: String) -> URL?
@discardableResult func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError>
@discardableResult func loadFileFromURL(_ url: URL, fileExtension: String) async -> Result<URL, MediaProviderError>
}
extension MediaProviderProtocol {
@@ -50,11 +50,11 @@ extension MediaProviderProtocol {
await loadImageFromSource(source, avatarSize: nil)
}
func imageFromURLString(_ urlString: String?) -> UIImage? {
imageFromURLString(urlString, avatarSize: nil)
func imageFromURL(_ url: URL?) -> UIImage? {
imageFromURL(url, avatarSize: nil)
}
@discardableResult func loadImageFromURLString(_ urlString: String) async -> Result<UIImage, MediaProviderError> {
await loadImageFromURLString(urlString, avatarSize: nil)
@discardableResult func loadImageFromURL(_ url: URL) async -> Result<UIImage, MediaProviderError> {
await loadImageFromURL(url, avatarSize: nil)
}
}
@@ -29,8 +29,8 @@ class MediaProxy: MediaProxyProtocol {
self.clientQueue = clientQueue
}
func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy {
.init(urlString: urlString)
func mediaSourceForURL(_ url: URL) -> MediaSourceProxy {
.init(url: url)
}
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data {
@@ -17,7 +17,7 @@
import Foundation
protocol MediaProxyProtocol {
func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy
func mediaSourceForURL(_ url: URL) -> MediaSourceProxy
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data
@@ -24,12 +24,13 @@ struct MediaSourceProxy: Hashable {
underlyingSource = source
}
init(urlString: String) {
underlyingSource = mediaSourceFromUrl(url: urlString)
init(url: URL) {
underlyingSource = mediaSourceFromUrl(url: url.absoluteString)
}
var url: String {
underlyingSource.url()
var url: URL! {
// Expecting these to always be valid URLs
URL(string: underlyingSource.url())
}
}
@@ -26,11 +26,11 @@ struct MockMediaProvider: MediaProviderProtocol {
.failure(.failedRetrievingImage)
}
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? {
guard urlString != nil else {
func imageFromURL(_ url: URL?, avatarSize: AvatarSize?) -> UIImage? {
guard url != nil else {
return nil
}
if let avatarSize {
switch avatarSize {
case .room:
@@ -41,8 +41,8 @@ struct MockMediaProvider: MediaProviderProtocol {
}
return UIImage(systemName: "photo")
}
func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
func loadImageFromURL(_ url: URL, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError> {
guard let image = UIImage(systemName: "photo") else {
fatalError()
}
@@ -58,11 +58,11 @@ struct MockMediaProvider: MediaProviderProtocol {
.failure(.failedRetrievingFile)
}
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? {
func fileFromURL(_ url: URL?, fileExtension: String) -> URL? {
nil
}
@discardableResult func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError> {
@discardableResult func loadFileFromURL(_ url: URL, fileExtension: String) async -> Result<URL, MediaProviderError> {
.failure(.failedRetrievingFile)
}
}