Files
session-ios/Session/Utilities/ImageLoading+Convenience.swift

232 lines
7.4 KiB
Swift

// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SwiftUI
import UniformTypeIdentifiers
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
// MARK: - ImageDataManager.DataSource Convenience
public extension ImageDataManager.DataSource {
static func from(
attachment: Attachment,
using dependencies: Dependencies
) -> ImageDataManager.DataSource? {
guard
attachment.isVisualMedia,
let path: String = try? dependencies[singleton: .attachmentManager]
.path(for: attachment.downloadUrl)
else { return nil }
if attachment.isVideo {
/// Videos need special handling so handle those specially
return .videoUrl(
URL(fileURLWithPath: path),
(UTType(sessionMimeType: attachment.contentType) ?? .invalid),
attachment.sourceFilename,
dependencies[singleton: .attachmentManager]
)
}
return .url(URL(fileURLWithPath: path))
}
static func thumbnailFrom(
attachment: Attachment,
size: ImageDataManager.ThumbnailSize,
using dependencies: Dependencies
) -> ImageDataManager.DataSource? {
guard let path: String = try? dependencies[singleton: .attachmentManager].path(for: attachment.downloadUrl) else {
return nil
}
return thumbnailFrom(
utType: (UTType(sessionMimeType: attachment.contentType) ?? .invalid),
path: path,
sourceFilename: attachment.sourceFilename,
size: size,
using: dependencies
)
}
static func thumbnailFrom(
quoteViewModel: QuoteViewModel,
using dependencies: Dependencies
) -> ImageDataManager.DataSource? {
guard
let info: QuoteViewModel.AttachmentInfo = quoteViewModel.quotedAttachmentInfo,
let path: String = try? dependencies[singleton: .attachmentManager]
.path(for: info.downloadUrl)
else { return nil }
return .thumbnailFrom(
utType: info.utType,
path: path,
sourceFilename: info.sourceFilename,
size: .small,
using: dependencies
)
}
static func thumbnailFrom(
utType: UTType,
path: String,
sourceFilename: String? = nil,
size: ImageDataManager.ThumbnailSize,
using dependencies: Dependencies
) -> ImageDataManager.DataSource? {
guard utType != .invalid else { return nil }
/// Can't thumbnail animated images so just load the full file in this case
if utType.isAnimated {
return .url(URL(fileURLWithPath: path))
}
/// Videos have a custom method for generating their thumbnails so use that instead
if utType.isVideo {
return .videoUrl(
URL(fileURLWithPath: path),
utType,
sourceFilename,
dependencies[singleton: .attachmentManager]
)
}
return .urlThumbnail(
URL(fileURLWithPath: path),
size,
dependencies[singleton: .attachmentManager]
)
}
}
// MARK: - ImageDataManagerType Convenience
public extension ImageDataManagerType {
@MainActor
func loadImage(
attachment: Attachment,
using dependencies: Dependencies,
onComplete: @MainActor @escaping (ImageDataManager.FrameBuffer?) -> Void = { _ in }
) {
guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from(
attachment: attachment,
using: dependencies
) else { return onComplete(nil) }
load(source, onComplete: onComplete)
}
@MainActor
func loadThumbnail(
size: ImageDataManager.ThumbnailSize,
attachment: Attachment,
using dependencies: Dependencies,
onComplete: @MainActor @escaping (ImageDataManager.FrameBuffer?) -> Void = { _ in }
) {
guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.thumbnailFrom(
attachment: attachment,
size: size,
using: dependencies
) else { return onComplete(nil) }
load(source, onComplete: onComplete)
}
}
// MARK: - SessionImageView Convenience
public extension SessionImageView {
@MainActor
func loadImage(from path: String, onComplete: (@MainActor (ImageDataManager.FrameBuffer?) -> Void)? = nil) {
loadImage(.url(URL(fileURLWithPath: path)), onComplete: onComplete)
}
@MainActor
func loadImage(
attachment: Attachment,
using dependencies: Dependencies,
onComplete: (@MainActor (ImageDataManager.FrameBuffer?) -> Void)? = nil
) {
guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.from(
attachment: attachment,
using: dependencies
) else {
onComplete?(nil)
return
}
loadImage(source, onComplete: onComplete)
}
@MainActor
func loadThumbnail(
size: ImageDataManager.ThumbnailSize,
attachment: Attachment,
using dependencies: Dependencies,
onComplete: (@MainActor (ImageDataManager.FrameBuffer?) -> Void)? = nil
) {
guard let source: ImageDataManager.DataSource = ImageDataManager.DataSource.thumbnailFrom(
attachment: attachment,
size: size,
using: dependencies
) else {
onComplete?(nil)
return
}
loadImage(source, onComplete: onComplete)
}
@MainActor
func loadPlaceholder(seed: String, text: String, size: CGFloat, onComplete: (@MainActor (ImageDataManager.FrameBuffer?) -> Void)? = nil) {
loadImage(.placeholderIcon(seed: seed, text: text, size: size), onComplete: onComplete)
}
}
// MARK: - SessionAsyncImage Convenience
public extension SessionAsyncImage {
init(
attachment: Attachment,
thumbnailSize: ImageDataManager.ThumbnailSize,
using dependencies: Dependencies,
@ViewBuilder content: @escaping (Image) -> Content,
@ViewBuilder placeholder: @escaping () -> Placeholder
) {
let source: ImageDataManager.DataSource? = ImageDataManager.DataSource.thumbnailFrom(
attachment: attachment,
size: thumbnailSize,
using: dependencies
)
/// Fallback in case we don't have a valid source
self.init(
source: (source ?? .image("", nil)),
dataManager: dependencies[singleton: .imageDataManager],
content: content,
placeholder: placeholder
)
}
}
// MARK: - Attachment Convenience
public extension Attachment {
func quoteAttachmentInfo(using dependencies: Dependencies) -> QuoteViewModel.AttachmentInfo? {
guard let utType: UTType = UTType(sessionMimeType: contentType) else { return nil }
return QuoteViewModel.AttachmentInfo(
id: id,
utType: utType,
isVoiceMessage: (variant == .voiceMessage),
downloadUrl: downloadUrl,
sourceFilename: sourceFilename,
thumbnailSource: .thumbnailFrom(attachment: self, size: .small, using: dependencies)
)
}
}