// // 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: OldDraft @EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var uiState: ComposeUIState @State var isShowingAssetPickerPopover = false @State var isShowingCreateDrawing = false @Environment(\.colorScheme) var colorScheme: ColorScheme @Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass? var body: some View { Group { ForEach(draft.attachments) { (attachment) in ComposeAttachmentRow( draft: draft, attachment: attachment ) .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(.accentColor) .frame(height: cellHeight / 2) .sheetOrPopover(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(.accentColor) .frame(height: cellHeight / 2) .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2)) Button(action: self.togglePoll) { Label(draft.poll == nil ? "Add a poll" : "Remove poll", systemImage: "chart.bar.doc.horizontal") } .disabled(!canAddPoll) .foregroundColor(.accentColor) .frame(height: cellHeight / 2) .listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2)) } .onAppear(perform: self.didAppear) } private var addButtonImageName: String { switch colorScheme { case .dark: return "photo.fill" case .light: return "photo" @unknown default: return "photo" } } private var canAddAttachment: Bool { if mastodonController.instanceFeatures.mastodonAttachmentRestrictions { return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image } && draft.poll == nil } else { return true } } private var canAddPoll: Bool { if mastodonController.instanceFeatures.pollsAndAttachments { return true } else { return draft.attachments.isEmpty } } private func didAppear() { if #available(iOS 16.0, *) { // these appearance proxy hacks are no longer necessary } else { let proxy = UITableView.appearance(whenContainedInInstancesOf: [ComposeHostingController.self]) // enable drag and drop to reorder on iPhone proxy.dragInteractionEnabled = true proxy.isScrollEnabled = false } } private func assetPickerPopover() -> some View { ComposeAssetPicker(draft: draft, delegate: uiState.delegate?.assetPickerDelegate) .onDisappear { // on iPadOS 16, this is necessary to dismiss the popover when collapsing from regular -> compact size class // otherwise, the popover isn't visible but it's still "presented", so the sheet can't be shown self.isShowingAssetPickerPopover = false } // on iPadOS 16, this is necessary to show the dark color in the popover arrow .background(Color(.appBackground)) .environment(\.colorScheme, .dark) .edgesIgnoringSafeArea(.bottom) .withSheetDetentsIfAvailable() } private func addAttachment() { // if #available(iOS 16.0, *) { // isShowingAssetPickerPopover = true // } else 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() } private func togglePoll() { UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil) withAnimation { draft.poll = draft.poll == nil ? OldDraft.Poll() : nil } } } fileprivate extension View { @available(iOS, obsoleted: 16.0) @ViewBuilder func sheetOrPopover(isPresented: Binding, @ViewBuilder content: @escaping () -> some View) -> some View { if #available(iOS 16.0, *) { self.modifier(SheetOrPopover(isPresented: isPresented, view: content)) } else { self.popover(isPresented: isPresented, content: content) } } @available(iOS, obsoleted: 16.0) @ViewBuilder func withSheetDetentsIfAvailable() -> some View { if #available(iOS 16.0, *) { self .presentationDetents([.medium, .large]) .presentationDragIndicator(.visible) } else { self } } } @available(iOS 16.0, *) struct SheetOrPopover: ViewModifier { @Binding var isPresented: Bool @ViewBuilder let view: () -> V @Environment(\.horizontalSizeClass) var sizeClass func body(content: Content) -> some View { if sizeClass == .compact { content.sheet(isPresented: $isPresented, content: view) } else { content.popover(isPresented: $isPresented, content: view) } } } //struct ComposeAttachmentsList_Previews: PreviewProvider { // static var previews: some View { // ComposeAttachmentsList() // } //}