From 30ef9cc6d09b96ad0da082ee36f37da85a564dc3 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 10 Nov 2021 16:57:27 -0500 Subject: [PATCH] Extract compose image into separate view --- Tusker.xcodeproj/project.pbxproj | 4 + .../Compose/ComposeAttachmentImage.swift | 80 +++++++++++++++++++ .../Compose/ComposeAttachmentRow.swift | 55 +------------ .../Compose/ComposeAttachmentsList.swift | 11 +++ 4 files changed, 96 insertions(+), 54 deletions(-) create mode 100644 Tusker/Screens/Compose/ComposeAttachmentImage.swift diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index d23d669207..f4db11cce0 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -308,6 +308,7 @@ D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */; }; D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */; }; D6D4DDF0212518A200E1C4BB /* TuskerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */; }; + D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */; }; D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */; }; D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */; }; D6DEA0DE268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DEA0DC268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift */; }; @@ -719,6 +720,7 @@ D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TuskerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerUITests.swift; sourceTree = ""; }; D6D4DDF1212518A200E1C4BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentImage.swift; sourceTree = ""; }; D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWarningCopyMode.swift; sourceTree = ""; }; D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preferences+Notification.swift"; sourceTree = ""; }; D6DEA0DC268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmLoadMoreTableViewCell.swift; sourceTree = ""; }; @@ -1170,6 +1172,7 @@ D662AEF1263A4BE10082A153 /* ComposePollView.swift */, D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */, D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */, + D6DD2A3E273C1F4900386A6C /* ComposeAttachmentImage.swift */, D622757724EE133700B82A16 /* ComposeAssetPicker.swift */, D62275A524F1C81800B82A16 /* ComposeReplyView.swift */, D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */, @@ -2187,6 +2190,7 @@ D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */, D69693F42585941A00F4E116 /* UIWindowSceneDelegate+Close.swift in Sources */, D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */, + D6DD2A3F273C1F4900386A6C /* ComposeAttachmentImage.swift in Sources */, 0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */, D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */, D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */, diff --git a/Tusker/Screens/Compose/ComposeAttachmentImage.swift b/Tusker/Screens/Compose/ComposeAttachmentImage.swift new file mode 100644 index 0000000000..5a7a04060f --- /dev/null +++ b/Tusker/Screens/Compose/ComposeAttachmentImage.swift @@ -0,0 +1,80 @@ +// +// ComposeAttachmentImage.swift +// Tusker +// +// Created by Shadowfacts on 11/10/21. +// Copyright © 2021 Shadowfacts. All rights reserved. +// + +import SwiftUI +import Photos + +struct ComposeAttachmentImage: View { + let attachment: CompositionAttachment + let fullSize: Bool + + @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 image = 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 let .image(image): + self.image = image + case let .asset(asset): + 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 let .video(url): + 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) + } + case let .drawing(drawing): + image = drawing.imageInLightMode(from: drawing.bounds) + imageContentMode = .fit + imageBackgroundColor = .white + } + } +} + +struct ComposeAttachmentImage_Previews: PreviewProvider { + static var previews: some View { + ComposeAttachmentImage(attachment: CompositionAttachment(data: .image(UIImage())), fullSize: false) + } +} diff --git a/Tusker/Screens/Compose/ComposeAttachmentRow.swift b/Tusker/Screens/Compose/ComposeAttachmentRow.swift index 96d0e3568f..a8ed89eb7b 100644 --- a/Tusker/Screens/Compose/ComposeAttachmentRow.swift +++ b/Tusker/Screens/Compose/ComposeAttachmentRow.swift @@ -18,17 +18,12 @@ struct ComposeAttachmentRow: View { @EnvironmentObject var uiState: ComposeUIState @State private var mode: Mode = .allowEntry - @State private var image: UIImage? = nil - @State private var imageContentMode: ContentMode = .fill - @State private var imageBackgroundColor: Color = .black @State private var isShowingTextRecognitionFailedAlert = false @State private var textRecognitionErrorMessage: String? = nil - @Environment(\.colorScheme) private var colorScheme: ColorScheme - var body: some View { HStack(alignment: .center, spacing: 4) { - imageView + ComposeAttachmentImage(attachment: attachment, fullSize: false) .frame(width: 80, height: 80) .cornerRadius(8) .contextMenu { @@ -71,7 +66,6 @@ struct ComposeAttachmentRow: View { // .foregroundColor(.blue) // } } - .onAppear(perform: self.loadImage) .onReceive(attachment.$attachmentDescription) { (newDesc) in if newDesc.isEmpty { uiState.attachmentsMissingDescriptions.insert(attachment.id) @@ -88,53 +82,6 @@ struct ComposeAttachmentRow: View { } } - @ViewBuilder - private var imageView: some View { - if let image = image { - Image(uiImage: image) - .resizable() - .aspectRatio(contentMode: imageContentMode) - .background(imageBackgroundColor) - } else { - Image(systemName: placeholderImageName) - } - } - - private var placeholderImageName: String { - switch colorScheme { - case .dark: - return "photo.fill" - case .light: - return "photo" - @unknown default: - return "photo" - } - } - - private func loadImage() { - switch attachment.data { - case let .image(image): - self.image = image - case let .asset(asset): - let 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 let .video(url): - 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) - } - case let .drawing(drawing): - image = drawing.imageInLightMode(from: drawing.bounds) - imageContentMode = .fit - imageBackgroundColor = .white - } - } - private func removeAttachment() { withAnimation { draft.attachments.removeAll { $0.id == attachment.id } diff --git a/Tusker/Screens/Compose/ComposeAttachmentsList.swift b/Tusker/Screens/Compose/ComposeAttachmentsList.swift index 9ed91af814..2acdb3c162 100644 --- a/Tusker/Screens/Compose/ComposeAttachmentsList.swift +++ b/Tusker/Screens/Compose/ComposeAttachmentsList.swift @@ -185,6 +185,17 @@ struct ComposeAttachmentsList: View { } } +fileprivate extension View { + @ViewBuilder + func onDragWithPreviewIfAvailable(_ data: @escaping () -> NSItemProvider, preview: () -> V) -> some View where V : View { + if #available(iOS 15.0, *) { + self.onDrag(data, preview: preview) + } else { + self.onDrag(data) + } + } +} + //struct ComposeAttachmentsList_Previews: PreviewProvider { // static var previews: some View { // ComposeAttachmentsList()