feat: add multiplex image nodes to StatusNode with progress loading supports

This commit is contained in:
CMK 2021-06-21 01:38:13 +08:00
parent 6f8666aaa8
commit 1156af3d4c
2 changed files with 67 additions and 7 deletions

View File

@ -10,6 +10,7 @@ import Combine
import AsyncDisplayKit
import CoreDataStack
import ActiveLabel
import func AVFoundation.AVMakeRect
protocol StatusNodeDelegate: AnyObject {
func statusNode(_ node: StatusNode, statusContentTextNode: ASMetaEditableTextNode, didSelectActiveEntityType type: ActiveEntityType)
@ -20,6 +21,7 @@ final class StatusNode: ASCellNode {
var disposeBag = Set<AnyCancellable>()
var timestamp: Date
var timestampSubscription: AnyCancellable?
weak var delegate: StatusNodeDelegate? // needs assign on main queue
static let avatarImageSize = CGSize(width: 42, height: 42)
@ -51,7 +53,6 @@ final class StatusNode: ASCellNode {
// node.shouldRenderProgressImages = true
return node
}()
let nameTextNode = ASTextNode()
let nameDotTextNode = ASTextNode()
let dateTextNode = ASTextNode()
@ -62,10 +63,29 @@ final class StatusNode: ASCellNode {
return node
}()
let mosaicImageViewModel: MosaicImageViewModel
let mediaMultiplexImageNodes: [ASMultiplexImageNode]
init(status: Status) {
timestamp = (status.reblog ?? status).createdAt
let _mosaicImageViewModel: MosaicImageViewModel = {
let mediaAttachments = Array((status.reblog ?? status).mediaAttachments ?? []).sorted { $0.index.compare($1.index) == .orderedAscending }
return MosaicImageViewModel(mediaAttachments: mediaAttachments)
}()
mosaicImageViewModel = _mosaicImageViewModel
mediaMultiplexImageNodes = {
var imageNodes: [ASMultiplexImageNode] = []
for _ in 0..<_mosaicImageViewModel.metas.count {
let imageNode = ASMultiplexImageNode() // TODO: adapt downloader
imageNode.downloadsIntermediateImages = true
imageNode.imageIdentifiers = ["url", "previewURL"].map { $0 as NSString } // quality in descending order
imageNodes.append(imageNode)
}
return imageNodes
}()
super.init()
print("meta: \(mosaicImageViewModel.metas.count), nodes: \(mediaMultiplexImageNodes.count)")
automaticallyManagesSubnodes = true
if let url = (status.reblog ?? status).author.avatarImageURL() {
@ -98,6 +118,10 @@ final class StatusNode: ASCellNode {
) {
statusContentTextNode.attributedText = parseResult.trimmedAttributedString(appearance: StatusNode.statusContentAppearance)
}
for imageNode in mediaMultiplexImageNodes {
imageNode.dataSource = self
}
}
override func didEnterDisplayState() {
@ -112,6 +136,7 @@ final class StatusNode: ASCellNode {
])
}
// FIXME: needs move to other only once called callback in life cycle like: `viewDidLoad`
statusContentTextNode.textView.isEditable = false
statusContentTextNode.textView.textDragInteraction?.isEnabled = false
statusContentTextNode.textView.linkTextAttributes = [
@ -153,16 +178,34 @@ final class StatusNode: ASCellNode {
let verticalStack = ASStackLayoutSpec.vertical()
verticalStack.spacing = 10
verticalStack.children = [
var verticalStackChildren: [ASLayoutElement] = [
headerStack,
statusContentTextNode,
]
if !mediaMultiplexImageNodes.isEmpty {
for (imageNode, meta) in zip(mediaMultiplexImageNodes, mosaicImageViewModel.metas) {
imageNode.style.preferredSize = AVMakeRect(aspectRatio: meta.size, insideRect: CGRect(origin: .zero, size: constrainedSize.max)).size
let layout = ASRatioLayoutSpec(ratio: meta.size.height / meta.size.width, child: imageNode)
verticalStackChildren.append(layout)
}
}
verticalStack.children = verticalStackChildren
return verticalStack
}
}
//extension StatusNode: ASImageDownloaderProtocol {
// func downloadImage(with URL: URL, callbackQueue: DispatchQueue, downloadProgress: ASImageDownloaderProgress?, completion: @escaping ASImageDownloaderCompletion) -> Any? {
//
// }
//
// func cancelImageDownload(forIdentifier downloadIdentifier: Any) {
//
// }
//}
// MARK: - ASEditableTextNodeDelegate
extension StatusNode: ASMetaEditableTextNodeDelegate {
func metaEditableTextNode(_ textNode: ASMetaEditableTextNode, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
@ -175,3 +218,21 @@ extension StatusNode: ASMetaEditableTextNodeDelegate {
return false
}
}
// MARK: - ASMultiplexImageNodeDataSource
extension StatusNode: ASMultiplexImageNodeDataSource {
func multiplexImageNode(_ imageNode: ASMultiplexImageNode, urlForImageIdentifier imageIdentifier: ASImageIdentifier) -> URL? {
guard let imageNodeIndex = mediaMultiplexImageNodes.firstIndex(of: imageNode) else { return nil }
guard imageNodeIndex < mosaicImageViewModel.metas.count else { return nil }
let meta = mosaicImageViewModel.metas[imageNodeIndex]
switch imageIdentifier {
case "url" as NSString:
return meta.url
case "previewURL" as NSString:
return meta.priviewURL
default:
assertionFailure()
return nil
}
}
}

View File

@ -16,16 +16,14 @@ struct MosaicImageViewModel {
init(mediaAttachments: [Attachment]) {
var metas: [MosaicMeta] = []
for element in mediaAttachments where element.type == .image {
// Display original on the iPad/Mac
guard let previewURL = element.previewURL else { continue }
let urlString = UIDevice.current.userInterfaceIdiom == .phone ? previewURL : element.url
guard let meta = element.meta,
let width = meta.original?.width,
let height = meta.original?.height,
let url = URL(string: urlString) else {
let url = URL(string: element.url) else {
continue
}
let mosaicMeta = MosaicMeta(
priviewURL: element.previewURL.flatMap { URL(string: $0) },
url: url,
size: CGSize(width: width, height: height),
blurhash: element.blurhash,
@ -40,7 +38,8 @@ struct MosaicImageViewModel {
struct MosaicMeta {
static let edgeMaxLength: CGFloat = 20
let priviewURL: URL?
let url: URL
let size: CGSize
let blurhash: String?