forked from shadowfacts/Tusker
211 lines
7.6 KiB
Swift
211 lines
7.6 KiB
Swift
//
|
|
// 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
|
|
|
|
@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(.systemBackground))
|
|
.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 ? Draft.Poll() : nil
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate extension View {
|
|
@available(iOS, obsoleted: 16.0)
|
|
@ViewBuilder
|
|
func sheetOrPopover(isPresented: Binding<Bool>, @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<V: View>: 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()
|
|
// }
|
|
//}
|