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")
|
Label("Delete", systemImage: "trash")
|
||||||
}
|
}
|
||||||
} previewIfAvailable: {
|
} previewIfAvailable: {
|
||||||
AttachmentThumbnailView(attachment: attachment, fullSize: true)
|
ControllerView(controller: { controller.thumbnailController })
|
||||||
}
|
}
|
||||||
|
|
||||||
switch controller.descriptionMode {
|
switch controller.descriptionMode {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AttachmentThumbnailView.swift
|
// AttachmentThumbnailController.swift
|
||||||
// ComposeUI
|
// ComposeUI
|
||||||
//
|
//
|
||||||
// Created by Shadowfacts on 11/10/21.
|
// Created by Shadowfacts on 11/10/21.
|
||||||
|
@ -14,14 +14,16 @@ class AttachmentThumbnailController: ViewController {
|
||||||
let attachment: DraftAttachment
|
let attachment: DraftAttachment
|
||||||
|
|
||||||
@Published private var image: UIImage?
|
@Published private var image: UIImage?
|
||||||
|
@Published private var gifController: GIFController?
|
||||||
@Published private var fullSize: Bool = false
|
@Published private var fullSize: Bool = false
|
||||||
|
@Published private var imageBackground: Color?
|
||||||
|
|
||||||
init(attachment: DraftAttachment) {
|
init(attachment: DraftAttachment) {
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImageIfNecessary(fullSize: Bool) {
|
func loadImageIfNecessary(fullSize: Bool) {
|
||||||
guard image == nil || (fullSize && !self.fullSize) else {
|
if (gifController != nil) || (image != nil && fullSize == self.fullSize) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.fullSize = fullSize
|
self.fullSize = fullSize
|
||||||
|
@ -31,29 +33,48 @@ class AttachmentThumbnailController: ViewController {
|
||||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
|
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let size: CGSize
|
let isGIF = PHAssetResource.assetResources(for: asset).contains(where: { $0.uniformTypeIdentifier == UTType.gif.identifier })
|
||||||
if fullSize {
|
if isGIF {
|
||||||
size = PHImageManagerMaximumSize
|
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 {
|
} else {
|
||||||
// currently only used as thumbnail in ComposeAttachmentRow
|
let size: CGSize
|
||||||
size = CGSize(width: 80, height: 80)
|
if fullSize {
|
||||||
}
|
size = PHImageManagerMaximumSize
|
||||||
// todo: gifs
|
} else {
|
||||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
// currently only used as thumbnail in ComposeAttachmentRow
|
||||||
DispatchQueue.main.async {
|
size = CGSize(width: 80, height: 80)
|
||||||
self.image = image
|
}
|
||||||
|
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.image = image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .drawing(_):
|
case .drawing(let drawing):
|
||||||
// todo
|
image = drawing.imageInLightMode(from: drawing.bounds)
|
||||||
break
|
|
||||||
|
|
||||||
case .file(let url, let type):
|
case .file(let url, let type):
|
||||||
// todo: videos
|
if type.conforms(to: .movie) {
|
||||||
if let data = try? Data(contentsOf: url) {
|
let asset = AVURLAsset(url: url)
|
||||||
// todo: gifs
|
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||||
if type.conforms(to: .image),
|
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) {
|
let image = UIImage(data: data) {
|
||||||
if fullSize {
|
if fullSize {
|
||||||
image.prepareForDisplay { prepared in
|
image.prepareForDisplay { prepared in
|
||||||
|
@ -90,7 +111,9 @@ class AttachmentThumbnailController: ViewController {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var content: some SwiftUI.View {
|
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)
|
Image(uiImage: image)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(config.aspectRatio, contentMode: config.contentMode)
|
.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 {
|
private struct GIFViewWrapper: UIViewRepresentable {
|
||||||
typealias UIViewType = GIFImageView
|
typealias UIViewType = GIFImageView
|
||||||
|
|
||||||
@State private var controller: GIFController
|
@State var controller: GIFController
|
||||||
|
|
||||||
init(gifData: Data) {
|
|
||||||
self._controller = State(wrappedValue: GIFController(gifData: gifData))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> GIFImageView {
|
func makeUIView(context: Context) -> GIFImageView {
|
||||||
let view = GIFImageView()
|
let view = GIFImageView()
|
|
@ -134,7 +134,7 @@ private struct DraftRow: View {
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
ForEach(draft.draftAttachments) { attachment in
|
ForEach(draft.draftAttachments) { attachment in
|
||||||
AttachmentThumbnailView(attachment: attachment, fullSize: false)
|
ControllerView(controller: { AttachmentThumbnailController(attachment: attachment) })
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
|
|
|
@ -74,7 +74,8 @@ struct ZoomableScrollView<Content: View>: UIViewControllerRepresentable {
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
if host.view.intrinsicContentSize != lastIntrinsicSize {
|
if !host.view.intrinsicContentSize.equalTo(.zero),
|
||||||
|
host.view.intrinsicContentSize != lastIntrinsicSize {
|
||||||
self.lastIntrinsicSize = host.view.intrinsicContentSize
|
self.lastIntrinsicSize = host.view.intrinsicContentSize
|
||||||
|
|
||||||
let maxHeight = view.bounds.height - view.safeAreaInsets.top - view.safeAreaInsets.bottom
|
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 widthScale = maxWidth / host.view.intrinsicContentSize.width
|
||||||
let minScale = min(widthScale, heightScale)
|
let minScale = min(widthScale, heightScale)
|
||||||
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
let maxScale = minScale >= 1 ? minScale + 2 : 2
|
||||||
print("min: \(minScale), max: \(maxScale)")
|
|
||||||
scrollView.minimumZoomScale = minScale
|
scrollView.minimumZoomScale = minScale
|
||||||
scrollView.maximumZoomScale = maxScale
|
scrollView.maximumZoomScale = maxScale
|
||||||
scrollView.zoomScale = minScale
|
scrollView.zoomScale = minScale
|
||||||
|
|
Loading…
Reference in New Issue