From 68351897b0879c0c7b72f9f6d306ebf5ffcbcfad Mon Sep 17 00:00:00 2001 From: Marcin Czachursk Date: Mon, 9 Jan 2023 13:05:02 +0100 Subject: [PATCH] Add blurhash support for main status image. --- Vernissage.xcodeproj/project.pbxproj | 4 ++ .../CoreData/AttachmentData+Attachment.swift | 8 ++++ .../AttachmentData+CoreDataProperties.swift | 14 +++++-- Vernissage/Extensions/Status+ImageSize.swift | 26 +++++++++++++ .../Vernissage.xcdatamodel/contents | 2 + Vernissage/Views/HomeFeedView.swift | 5 ++- Vernissage/Views/StatusView.swift | 38 +++++++++++++++---- Vernissage/Widgets/UserProfileStatuses.swift | 5 ++- 8 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 Vernissage/Extensions/Status+ImageSize.swift diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 4fd315b..44de706 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ F86B7214296BFDCE00EE59EC /* UserProfileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7213296BFDCE00EE59EC /* UserProfileHeader.swift */; }; F86B7216296BFFDA00EE59EC /* UserProfileStatuses.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7215296BFFDA00EE59EC /* UserProfileStatuses.swift */; }; F86B7218296C27C100EE59EC /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7217296C27C100EE59EC /* ActionButton.swift */; }; + F86B721C296C394000EE59EC /* Status+ImageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B721B296C394000EE59EC /* Status+ImageSize.swift */; }; F88ABD9229686F1C004EF61E /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88ABD9129686F1C004EF61E /* MemoryCache.swift */; }; F88ABD9429687CA4004EF61E /* ComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88ABD9329687CA4004EF61E /* ComposeView.swift */; }; F88C246C295C37B80006098B /* VernissageApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C246B295C37B80006098B /* VernissageApp.swift */; }; @@ -129,6 +130,7 @@ F86B7213296BFDCE00EE59EC /* UserProfileHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHeader.swift; sourceTree = ""; }; F86B7215296BFFDA00EE59EC /* UserProfileStatuses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileStatuses.swift; sourceTree = ""; }; F86B7217296C27C100EE59EC /* ActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = ""; }; + F86B721B296C394000EE59EC /* Status+ImageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Status+ImageSize.swift"; sourceTree = ""; }; F88ABD9129686F1C004EF61E /* MemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = ""; }; F88ABD9329687CA4004EF61E /* ComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeView.swift; sourceTree = ""; }; F88ABD9529687D4D004EF61E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -218,6 +220,7 @@ F8210DE62966E1D1001D9973 /* Color+Assets.swift */, F8C14393296AF21B001FE31D /* Double+Round.swift */, F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */, + F86B721B296C394000EE59EC /* Status+ImageSize.swift */, ); path = Extensions; sourceTree = ""; @@ -456,6 +459,7 @@ F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */, F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */, F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */, + F86B721C296C394000EE59EC /* Status+ImageSize.swift in Sources */, F8984E4D296B648000A2610F /* UIImage+Blurhash.swift in Sources */, F897978A2968314A00B22335 /* LoadingIndicator.swift in Sources */, F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */, diff --git a/Vernissage/CoreData/AttachmentData+Attachment.swift b/Vernissage/CoreData/AttachmentData+Attachment.swift index 5449ddb..65ff8a2 100644 --- a/Vernissage/CoreData/AttachmentData+Attachment.swift +++ b/Vernissage/CoreData/AttachmentData+Attachment.swift @@ -17,5 +17,13 @@ extension AttachmentData { self.remoteUrl = attachment.remoteUrl self.text = attachment.description self.type = attachment.type.rawValue + + if let width = (attachment.meta as? ImageMetadata)?.original?.width { + self.metaImageWidth = Int32(width) + } + + if let height = (attachment.meta as? ImageMetadata)?.original?.height { + self.metaImageHeight = Int32(height) + } } } diff --git a/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift b/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift index ab3d45c..5847681 100644 --- a/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift +++ b/Vernissage/CoreData/AttachmentData+CoreDataProperties.swift @@ -3,10 +3,13 @@ // Copyright © 2023 Marcin Czachurski and the repository contributors. // Licensed under the MIT License. // + +// import Foundation import CoreData + extension AttachmentData { @nonobjc public class func fetchRequest() -> NSFetchRequest { @@ -15,6 +18,10 @@ extension AttachmentData { @NSManaged public var blurhash: String? @NSManaged public var data: Data + @NSManaged public var exifCamera: String? + @NSManaged public var exifCreatedDate: String? + @NSManaged public var exifExposure: String? + @NSManaged public var exifLens: String? @NSManaged public var id: String @NSManaged public var previewUrl: URL? @NSManaged public var remoteUrl: URL? @@ -22,13 +29,12 @@ extension AttachmentData { @NSManaged public var text: String? @NSManaged public var type: String @NSManaged public var url: URL - @NSManaged public var exifCamera: String? - @NSManaged public var exifLens: String? - @NSManaged public var exifExposure: String? - @NSManaged public var exifCreatedDate: String? + @NSManaged public var metaImageWidth: Int32 + @NSManaged public var metaImageHeight: Int32 @NSManaged public var statusRelation: StatusData? } extension AttachmentData : Identifiable { + } diff --git a/Vernissage/Extensions/Status+ImageSize.swift b/Vernissage/Extensions/Status+ImageSize.swift new file mode 100644 index 0000000..4051260 --- /dev/null +++ b/Vernissage/Extensions/Status+ImageSize.swift @@ -0,0 +1,26 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation +import MastodonSwift + +extension Status { + public func getImageWidth() -> Int32? { + if let width = (self.mediaAttachments.first?.meta as? ImageMetadata)?.original?.width { + return Int32(width) + } else { + return nil + } + } + + public func getImageHeight() -> Int32? { + if let height = (self.mediaAttachments.first?.meta as? ImageMetadata)?.original?.height { + return Int32(height) + } else { + return nil + } + } +} diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents b/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents index 2e42a0d..b41c334 100644 --- a/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents +++ b/Vernissage/Vernissage.xcdatamodeld/Vernissage.xcdatamodel/contents @@ -32,6 +32,8 @@ + + diff --git a/Vernissage/Views/HomeFeedView.swift b/Vernissage/Views/HomeFeedView.swift index 3fea099..67c9abd 100644 --- a/Vernissage/Views/HomeFeedView.swift +++ b/Vernissage/Views/HomeFeedView.swift @@ -22,7 +22,10 @@ struct HomeFeedView: View { ScrollView { LazyVGrid(columns: gridColumns) { ForEach(dbStatuses, id: \.self) { item in - NavigationLink(destination: StatusView(statusId: item.id) + NavigationLink(destination: StatusView(statusId: item.id, + imageBlurhash: item.attachments().first?.blurhash, + imageWidth: item.attachments().first?.metaImageWidth, + imageHeight: item.attachments().first?.metaImageHeight) .environmentObject(applicationState)) { ImageRow(attachments: item.attachments()) } diff --git a/Vernissage/Views/StatusView.swift b/Vernissage/Views/StatusView.swift index 093b9ed..d4a1a9b 100644 --- a/Vernissage/Views/StatusView.swift +++ b/Vernissage/Views/StatusView.swift @@ -11,6 +11,9 @@ import AVFoundation struct StatusView: View { @EnvironmentObject var applicationState: ApplicationState @State var statusId: String + @State var imageBlurhash: String? + @State var imageWidth: Int32? + @State var imageHeight: Int32? @State private var showCompose = false @State private var statusData: StatusData? @@ -84,10 +87,17 @@ struct StatusView: View { } } else { VStack (alignment: .leading) { - Rectangle() - .fill(Color.placeholderText) - .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width) - + if let imageBlurhash, let uiImage = UIImage(blurHash: imageBlurhash, size: CGSize(width: 32, height: 32)) { + Image(uiImage: uiImage) + .resizable() + .frame(width: UIScreen.main.bounds.width, height: self.getImageHeight()) + } else { + Rectangle() + .fill(Color.placeholderText) + .frame(width: UIScreen.main.bounds.width, height: self.getImageHeight()) + .redacted(reason: .placeholder) + } + VStack(alignment: .leading) { UsernameRow(accountDisplayName: "Verylong Displayname", accountUsername: "@username") @@ -103,10 +113,11 @@ struct StatusView: View { LabelIcon(iconName: "camera.aperture", value: "Viltrox 24mm F1.8 E") LabelIcon(iconName: "timelapse", value: "24.0 mm, f/1.8, 1/640s, ISO 100") LabelIcon(iconName: "calendar", value: "2 Oct 2022") - }.padding(8) + } + .padding(8) + .redacted(reason: .placeholder) + .animatePlaceholder(isLoading: .constant(true)) } - .redacted(reason: .placeholder) - .animatePlaceholder(isLoading: .constant(true)) } } .navigationBarTitle("Details") @@ -143,6 +154,19 @@ struct StatusView: View { exifCreatedDate = attachmentData.exifCreatedDate exifLens = attachmentData.exifLens } + + private func getImageHeight() -> Double { + if let imageHeight = self.imageHeight, let imageWidth = self.imageWidth, imageHeight > 0 && imageWidth > 0 { + return self.calculateHeight(width: Double(imageWidth), height: Double(imageHeight)) + } + + return UIScreen.main.bounds.width * 0.75 + } + + private func calculateHeight(width: Double, height: Double) -> CGFloat { + let divider = width / UIScreen.main.bounds.size.width + return height / divider + } } struct StatusView_Previews: PreviewProvider { diff --git a/Vernissage/Widgets/UserProfileStatuses.swift b/Vernissage/Widgets/UserProfileStatuses.swift index bf5b06f..60b4414 100644 --- a/Vernissage/Widgets/UserProfileStatuses.swift +++ b/Vernissage/Widgets/UserProfileStatuses.swift @@ -20,7 +20,10 @@ struct UserProfileStatuses: View { VStack(alignment: .center) { if firstLoadFinished == true { ForEach(self.statuses, id: \.id) { item in - NavigationLink(destination: StatusView(statusId: item.id) + NavigationLink(destination: StatusView(statusId: item.id, + imageBlurhash: item.mediaAttachments.first?.blurhash, + imageWidth: item.getImageWidth(), + imageHeight: item.getImageHeight()) .environmentObject(applicationState)) { ImageRowAsync(attachments: item.mediaAttachments) }