mastodon-ios/MastodonSDK/Sources/MastodonUI/View/Content/MediaView+Configuration.swift

330 lines
11 KiB
Swift

//
// MediaView+Configuration.swift
// TwidereX
//
// Created by Cirno MainasuK on 2021-10-14.
// Copyright © 2021 Twidere. All rights reserved.
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import Photos
import AlamofireImage
import MastodonCore
import MastodonSDK
extension MediaView {
public class Configuration: Hashable {
var disposeBag = Set<AnyCancellable>()
public let info: Info
public let blurhash: String?
public let index: Int
public let total: Int
@Published public var isReveal = true
@Published public var previewImage: UIImage?
@Published public var blurhashImage: UIImage?
public var blurhashImageDisposeBag = Set<AnyCancellable>()
public init(
info: MediaView.Configuration.Info,
blurhash: String?,
index: Int,
total: Int
) {
self.info = info
self.blurhash = blurhash
self.index = index
self.total = total
}
public var aspectRadio: CGSize {
switch info {
case .image(let info): return info.aspectRadio
case .gif(let info): return info.aspectRadio
case .video(let info): return info.aspectRadio
}
}
public var previewURL: String? {
switch info {
case .image(let info):
return info.assetURL
case .gif(let info):
return info.previewURL
case .video(let info):
return info.previewURL
}
}
public var assetURL: String? {
switch info {
case .image(let info):
return info.assetURL
case .gif(let info):
return info.assetURL
case .video(let info):
return info.assetURL
}
}
public var resourceType: PHAssetResourceType {
switch info {
case .image:
return .photo
case .gif:
return .video
case .video:
return .video
}
}
public static func == (lhs: MediaView.Configuration, rhs: MediaView.Configuration) -> Bool {
return lhs.info == rhs.info
&& lhs.blurhash == rhs.blurhash
&& lhs.isReveal == rhs.isReveal
}
public func hash(into hasher: inout Hasher) {
hasher.combine(info)
hasher.combine(blurhash)
}
}
}
extension MediaView.Configuration {
public enum Info: Hashable {
case image(info: ImageInfo)
case gif(info: VideoInfo)
case video(info: VideoInfo)
}
public struct ImageInfo: Hashable {
public let aspectRadio: CGSize
public let assetURL: String?
public let altDescription: String?
public init(
aspectRadio: CGSize,
assetURL: String?,
altDescription: String?
) {
self.aspectRadio = aspectRadio
self.assetURL = assetURL
self.altDescription = altDescription
}
}
public struct VideoInfo: Hashable {
public let aspectRadio: CGSize
public let assetURL: String?
public let previewURL: String?
public let altDescription: String?
public let durationMS: Int?
public init(
aspectRadio: CGSize,
assetURL: String?,
previewURL: String?,
altDescription: String?,
durationMS: Int?
) {
self.aspectRadio = aspectRadio
self.assetURL = assetURL
self.previewURL = previewURL
self.durationMS = durationMS
self.altDescription = altDescription
}
}
}
extension MediaView.Configuration {
public func load() {
if let previewURL = previewURL,
let url = URL(string: previewURL)
{
let placeholder = UIImage.placeholder(color: .systemGray6)
let request = URLRequest(url: url)
ImageDownloader.default.download(request, completion: { [weak self] response in
guard let self = self else { return }
switch response.result {
case .success(let image):
self.previewImage = image
case .failure:
self.previewImage = placeholder
}
})
}
if let assetURL = assetURL,
let blurhash = blurhash
{
BlurhashImageCacheService.shared.image(
blurhash: blurhash,
size: aspectRadio,
url: assetURL
)
.assign(to: \.blurhashImage, on: self)
.store(in: &blurhashImageDisposeBag)
}
}
}
extension MediaView {
public static func configuration(status: MastodonStatus) -> [MediaView.Configuration] {
func videoInfo(from attachment: MastodonAttachment) -> MediaView.Configuration.VideoInfo {
MediaView.Configuration.VideoInfo(
aspectRadio: attachment.size,
assetURL: attachment.assetURL,
previewURL: attachment.previewURL,
altDescription: attachment.altDescription,
durationMS: attachment.durationMS
)
}
// let status: StatusCompatible = status.reblog ?? status
let attachments = status.entity.mastodonAttachments
let configurations = attachments.enumerated().map { (idx, attachment) -> MediaView.Configuration in
let configuration: MediaView.Configuration = {
switch attachment.kind {
case .image:
let info = MediaView.Configuration.ImageInfo(
aspectRadio: attachment.size,
assetURL: attachment.assetURL,
altDescription: attachment.altDescription
)
return .init(
info: .image(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .video:
let info = videoInfo(from: attachment)
return .init(
info: .video(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .gifv:
let info = videoInfo(from: attachment)
return .init(
info: .gif(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .audio:
let info = videoInfo(from: attachment)
return .init(
info: .video(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
} // end switch
}()
configuration.load()
configuration.isReveal = status.entity.sensitive == true ? status.isSensitiveToggled : true
return configuration
}
return configurations
}
}
extension MediaView {
public static func configuration(status: Mastodon.Entity.StatusEdit) -> [MediaView.Configuration] {
func aspectRatio(from attachment: Mastodon.Entity.Attachment) -> CGSize? {
if let width = attachment.meta?.original?.width, let height = attachment.meta?.original?.height {
return CGSize(width: width, height: height)
} else if let width = attachment.meta?.width, let height = attachment.meta?.height {
return CGSize(width: width, height: height)
}
return nil
}
func videoInfo(from attachment: Mastodon.Entity.Attachment) -> MediaView.Configuration.VideoInfo? {
guard let aspectRatio = aspectRatio(from: attachment) else { return nil }
return MediaView.Configuration.VideoInfo(
aspectRadio: aspectRatio,
assetURL: attachment.url,
previewURL: attachment.previewURL,
altDescription: attachment.description,
durationMS: {
guard let duration = attachment.meta?.duration else {
return 0
}
return Int(duration)
}()
)
}
let attachments = status.mediaAttachments ?? []
let configurations = attachments.enumerated().compactMap { (idx, attachment) -> MediaView.Configuration? in
let configuration: MediaView.Configuration? = {
switch attachment.attachmentKind {
case .image:
guard let aspectRatio = aspectRatio(from: attachment) else { return nil }
let info = MediaView.Configuration.ImageInfo(
aspectRadio: aspectRatio,
assetURL: attachment.url,
altDescription: attachment.description
)
return .init(
info: .image(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .video:
guard let info = videoInfo(from: attachment) else { return nil }
return .init(
info: .video(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .gifv:
guard let info = videoInfo(from: attachment) else { return nil }
return .init(
info: .gif(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .audio:
guard let info = videoInfo(from: attachment) else { return nil }
return .init(
info: .video(info: info),
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
)
case .none:
return nil
} // end switch
}()
configuration?.load()
configuration?.isReveal = true
return configuration
}
return configurations
}
}