forked from shadowfacts/Tusker
Support gifs in new thumbnail controller and focused attachment view
This commit is contained in:
parent
ce3b8ba4b3
commit
02daf88db3
|
@ -155,7 +155,7 @@ class AttachmentRowController: ViewController {
|
|||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
} previewIfAvailable: {
|
||||
AttachmentThumbnailView(attachment: attachment, fullSize: true)
|
||||
ControllerView(controller: { controller.thumbnailController })
|
||||
}
|
||||
|
||||
switch controller.descriptionMode {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// AttachmentThumbnailView.swift
|
||||
// AttachmentThumbnailController.swift
|
||||
// ComposeUI
|
||||
//
|
||||
// Created by Shadowfacts on 11/10/21.
|
||||
|
@ -14,14 +14,16 @@ class AttachmentThumbnailController: ViewController {
|
|||
let attachment: DraftAttachment
|
||||
|
||||
@Published private var image: UIImage?
|
||||
@Published private var gifController: GIFController?
|
||||
@Published private var fullSize: Bool = false
|
||||
@Published private var imageBackground: Color?
|
||||
|
||||
init(attachment: DraftAttachment) {
|
||||
self.attachment = attachment
|
||||
}
|
||||
|
||||
func loadImageIfNecessary(fullSize: Bool) {
|
||||
guard image == nil || (fullSize && !self.fullSize) else {
|
||||
if (gifController != nil) || (image != nil && fullSize == self.fullSize) {
|
||||
return
|
||||
}
|
||||
self.fullSize = fullSize
|
||||
|
@ -31,29 +33,48 @@ class AttachmentThumbnailController: ViewController {
|
|||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
|
||||
return
|
||||
}
|
||||
let size: CGSize
|
||||
if fullSize {
|
||||
size = PHImageManagerMaximumSize
|
||||
let isGIF = PHAssetResource.assetResources(for: asset).contains(where: { $0.uniformTypeIdentifier == UTType.gif.identifier })
|
||||
if isGIF {
|
||||
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: nil) { data, typeIdentifier, orientation, info in
|
||||
guard let data else { return }
|
||||
if typeIdentifier == UTType.gif.identifier {
|
||||
self.gifController = GIFController(gifData: data)
|
||||
} else {
|
||||
let image = UIImage(data: data)
|
||||
DispatchQueue.main.async {
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// currently only used as thumbnail in ComposeAttachmentRow
|
||||
size = CGSize(width: 80, height: 80)
|
||||
}
|
||||
// todo: gifs
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
DispatchQueue.main.async {
|
||||
self.image = image
|
||||
let size: CGSize
|
||||
if fullSize {
|
||||
size = PHImageManagerMaximumSize
|
||||
} else {
|
||||
// currently only used as thumbnail in ComposeAttachmentRow
|
||||
size = CGSize(width: 80, height: 80)
|
||||
}
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
DispatchQueue.main.async {
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .drawing(_):
|
||||
// todo
|
||||
break
|
||||
|
||||
case .drawing(let drawing):
|
||||
image = drawing.imageInLightMode(from: drawing.bounds)
|
||||
|
||||
case .file(let url, let type):
|
||||
// todo: videos
|
||||
if let data = try? Data(contentsOf: url) {
|
||||
// todo: gifs
|
||||
if type.conforms(to: .image),
|
||||
if type.conforms(to: .movie) {
|
||||
let asset = AVURLAsset(url: url)
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
self.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
} else if let data = try? Data(contentsOf: url) {
|
||||
if type == .gif {
|
||||
self.gifController = GIFController(gifData: data)
|
||||
} else if type.conforms(to: .image),
|
||||
let image = UIImage(data: data) {
|
||||
if fullSize {
|
||||
image.prepareForDisplay { prepared in
|
||||
|
@ -90,7 +111,9 @@ class AttachmentThumbnailController: ViewController {
|
|||
|
||||
@ViewBuilder
|
||||
private var content: some SwiftUI.View {
|
||||
if let image = controller.image {
|
||||
if let gifController = controller.gifController {
|
||||
GIFViewWrapper(controller: gifController)
|
||||
} else if let image = controller.image {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(config.aspectRatio, contentMode: config.contentMode)
|
||||
|
@ -124,115 +147,10 @@ extension EnvironmentValues {
|
|||
}
|
||||
}
|
||||
|
||||
struct AttachmentThumbnailView: View {
|
||||
let attachment: DraftAttachment
|
||||
let fullSize: Bool
|
||||
|
||||
@State private var gifData: Data? = nil
|
||||
@State private var image: UIImage? = nil
|
||||
@State private var imageContentMode: ContentMode = .fill
|
||||
@State private var imageBackgroundColor: Color = .black
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme: ColorScheme
|
||||
|
||||
var body: some View {
|
||||
if let gifData {
|
||||
GIFViewWrapper(gifData: gifData)
|
||||
} else if let image {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: imageContentMode)
|
||||
.background(imageBackgroundColor)
|
||||
} else {
|
||||
Image(systemName: placeholderImageName)
|
||||
.onAppear(perform: self.loadImage)
|
||||
}
|
||||
}
|
||||
|
||||
private var placeholderImageName: String {
|
||||
switch colorScheme {
|
||||
case .light:
|
||||
return "photo"
|
||||
case .dark:
|
||||
return "photo.fill"
|
||||
@unknown default:
|
||||
return "photo"
|
||||
}
|
||||
}
|
||||
|
||||
private func loadImage() {
|
||||
switch attachment.data {
|
||||
case .asset(let id):
|
||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
|
||||
return
|
||||
}
|
||||
let size: CGSize
|
||||
if fullSize {
|
||||
size = PHImageManagerMaximumSize
|
||||
} else {
|
||||
// currently only used as thumbnail in ComposeAttachmentRow
|
||||
size = CGSize(width: 80, height: 80)
|
||||
}
|
||||
let isGIF = PHAssetResource.assetResources(for: asset).contains(where: { $0.uniformTypeIdentifier == UTType.gif.identifier })
|
||||
if isGIF {
|
||||
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: nil) { data, typeIdentifier, orientation, info in
|
||||
if typeIdentifier == UTType.gif.identifier {
|
||||
self.gifData = data
|
||||
} else if let data {
|
||||
let image = UIImage(data: data)
|
||||
DispatchQueue.main.async {
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
DispatchQueue.main.async {
|
||||
self.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case let .drawing(drawing):
|
||||
image = drawing.imageInLightMode(from: drawing.bounds)
|
||||
imageContentMode = .fit
|
||||
imageBackgroundColor = .white
|
||||
|
||||
case .file(let url, let type):
|
||||
if type.conforms(to: .movie) {
|
||||
let asset = AVURLAsset(url: url)
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
self.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
} else if let data = try? Data(contentsOf: url) {
|
||||
if type == .gif {
|
||||
self.gifData = data
|
||||
} else if type.conforms(to: .image),
|
||||
let image = UIImage(data: data) {
|
||||
if fullSize {
|
||||
image.prepareForDisplay {
|
||||
self.image = $0
|
||||
}
|
||||
} else {
|
||||
image.prepareThumbnail(of: CGSize(width: 80, height: 80)) {
|
||||
self.image = $0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct GIFViewWrapper: UIViewRepresentable {
|
||||
typealias UIViewType = GIFImageView
|
||||
|
||||
@State private var controller: GIFController
|
||||
|
||||
init(gifData: Data) {
|
||||
self._controller = State(wrappedValue: GIFController(gifData: gifData))
|
||||
}
|
||||
@State var controller: GIFController
|
||||
|
||||
func makeUIView(context: Context) -> GIFImageView {
|
||||
let view = GIFImageView()
|
|
@ -134,7 +134,7 @@ private struct DraftRow: View {
|
|||
|
||||
HStack(spacing: 8) {
|
||||
ForEach(draft.draftAttachments) { attachment in
|
||||
AttachmentThumbnailView(attachment: attachment, fullSize: false)
|
||||
ControllerView(controller: { AttachmentThumbnailController(attachment: attachment) })
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||
.frame(height: 50)
|
||||
|
|
|
@ -74,7 +74,8 @@ struct ZoomableScrollView<Content: View>: UIViewControllerRepresentable {
|
|||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
if host.view.intrinsicContentSize != lastIntrinsicSize {
|
||||
if !host.view.intrinsicContentSize.equalTo(.zero),
|
||||
host.view.intrinsicContentSize != lastIntrinsicSize {
|
||||
self.lastIntrinsicSize = host.view.intrinsicContentSize
|
||||
|
||||
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
||||
|
@ -83,7 +84,6 @@ struct ZoomableScrollView<Content: View>: UIViewControllerRepresentable {
|
|||
let widthScale = maxWidth / host.view.intrinsicContentSize.width
|
||||
let minScale = min(widthScale, heightScale)
|
||||
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
||||
print("min: \(minScale), max: \(maxScale)")
|
||||
scrollView.minimumZoomScale = minScale
|
||||
scrollView.maximumZoomScale = maxScale
|
||||
scrollView.zoomScale = minScale
|
||||
|
|
Loading…
Reference in New Issue