// // 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)) .onDrag { NSItemProvider(object: attachment) } } .onMove(perform: self.moveAttachments) .onDelete(perform: self.deleteAttachments) .conditionally(canAddAttachment) { $0.onInsert(of: CompositionAttachment.readableTypeIdentifiersForItemProvider, perform: self.insertAttachments) } Button(action: self.addAttachment) { Label("Add photo or video", systemImage: addButtonImageName) } .disabled(!canAddAttachment) .foregroundColor(.blue) .frame(height: cellHeight / 2) .popover(isPresented: $isShowingAssetPickerPopover, content: self.assetPickerPopover) .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2)) Button(action: self.createDrawing) { Label("Draw something", systemImage: "hand.draw") } .disabled(!canAddAttachment) .foregroundColor(.blue) .frame(height: cellHeight / 2) .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2)) } .frame(height: totalListHeight) .onAppear(perform: self.didAppear) .onReceive(draft.$attachments, perform: self.attachmentsChanged) } private var addButtonImageName: String { switch colorScheme { case .dark: return "photo.fill" case .light: return "photo" @unknown default: return "photo" } } private var canAddAttachment: Bool { switch mastodonController.instance?.instanceType { case nil: return false case .pleroma: return true case .mastodon: return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image } } } private var totalListHeight: CGFloat { let totalRowHeights = rowHeights.values.reduce(0, +) let totalPadding = CGFloat(draft.attachments.count) * cellPadding let addButtonHeight = cellHeight + cellPadding * 2 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 proxy.isScrollEnabled = false } 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() // } //}