// // ComposeAttachmentsList.swift // Tusker // // Created by Shadowfacts on 8/19/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import SwiftUI struct ComposeAttachmentsList: View { private let cellHeight: CGFloat = 80 private let cellPadding: CGFloat = 12 @ObservedObject var draft: Draft @EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var uiState: ComposeUIState @State var isShowingAssetPickerPopover = false @State var isShowingCreateDrawing = false @State var rowHeights = [UUID: CGFloat]() @Environment(\.colorScheme) var colorScheme: ColorScheme @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? var body: some View { List { ForEach(draft.attachments) { (attachment) in ComposeAttachmentRow( draft: draft, attachment: attachment ) { (newHeight) in // in case height changed callback is called after atachment is removed but before view hierarchy is updated if draft.attachments.contains(where: { $0.id == attachment.id }) { rowHeights[attachment.id] = newHeight } } .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2)) } .onMove(perform: self.moveAttachments) .onDelete(perform: self.deleteAttachments) .conditionally(canAddAttachment) { $0.onInsert(of: CompositionAttachment.readableTypeIdentifiersForItemProvider, perform: self.insertAttachments) } Button(action: self.addAttachment) { HStack { addButtonImage Text("Add image or video") } } .foregroundColor(.blue) .disabled(!canAddAttachment) .frame(height: cellHeight) .popover(isPresented: $isShowingAssetPickerPopover, content: self.assetPickerPopover) .contextMenu { Button(action: self.createDrawing) { if #available(iOS 14.0, *) { Label("Draw Something", systemImage: "hand.draw") } else { HStack { Text("Draw Something") Image(systemName: "hand.draw") } } } .disabled(!canAddAttachment) } } .frame(height: totalListHeight) .onAppear(perform: self.didAppear) .onReceive(draft.$attachments, perform: self.attachmentsChanged) } private var addButtonImage: Image { let name: String switch colorScheme { case .dark: name = "photo.fill" case .light: name = "photo" @unknown default: name = "photo" } return Image(systemName: name) } private var canAddAttachment: Bool { switch mastodonController.instance.instanceType { case .pleroma: return true case .mastodon: // todo: this technically allows invalid image/video combinations return draft.attachments.count < 4 } } private var totalListHeight: CGFloat { let totalRowHeights = rowHeights.values.reduce(0, +) let totalPadding = CGFloat(draft.attachments.count) * cellPadding let addButtonHeight = cellHeight + cellPadding return totalRowHeights + totalPadding + addButtonHeight } private func didAppear() { let proxy = UITableView.appearance(whenContainedInInstancesOf: [ComposeHostingController.self]) // enable drag and drop to reorder on iPhone proxy.dragInteractionEnabled = true } private func attachmentsChanged(attachments: [CompositionAttachment]) { var copy = rowHeights for k in copy.keys where !attachments.contains(where: { k == $0.id }) { copy.removeValue(forKey: k) } for attachment in attachments where !copy.keys.contains(attachment.id) { copy[attachment.id] = cellHeight } self.rowHeights = copy } private func assetPickerPopover() -> some View { ComposeAssetPicker(draft: draft, delegate: uiState.delegate?.assetPickerDelegate) .onDisappear { self.isShowingAssetPickerPopover = false } .environment(\.colorScheme, .dark) .edgesIgnoringSafeArea(.bottom) } private func addAttachment() { if horizontalSizeClass == .regular { isShowingAssetPickerPopover = true } else { uiState.delegate?.presentAssetPickerSheet() } } private func moveAttachments(from source: IndexSet, to destination: Int) { draft.attachments.move(fromOffsets: source, toOffset: destination) } private func deleteAttachments(at indices: IndexSet) { draft.attachments.remove(atOffsets: indices) } private func insertAttachments(at offset: Int, itemProviders: [NSItemProvider]) { for provider in itemProviders where provider.canLoadObject(ofClass: CompositionAttachment.self) { guard canAddAttachment else { break } provider.loadObject(ofClass: CompositionAttachment.self) { (object, error) in guard let attachment = object as? CompositionAttachment else { return } DispatchQueue.main.async { self.draft.attachments.insert(attachment, at: offset) } } } } private func createDrawing() { uiState.composeDrawingMode = .createNew uiState.delegate?.presentComposeDrawing() } } //struct ComposeAttachmentsList_Previews: PreviewProvider { // static var previews: some View { // ComposeAttachmentsList() // } //}