Remove old compose rewrite code

This commit is contained in:
Shadowfacts 2024-12-15 21:04:29 -05:00
parent 6730575aed
commit e0e9d4a185
6 changed files with 72 additions and 396 deletions

View File

@ -1,182 +0,0 @@
//
// AttachmentRowView.swift
// ComposeUI
//
// Created by Shadowfacts on 8/18/24.
//
import SwiftUI
import InstanceFeatures
import Vision
struct AttachmentRowView: View {
@ObservedObject var attachment: DraftAttachment
@State private var isRecognizingText = false
@State private var textRecognitionError: (any Error)?
private var thumbnailSize: CGFloat {
#if os(visionOS)
120
#else
80
#endif
}
var body: some View {
HStack(alignment: .center, spacing: 4) {
thumbnailView
descriptionView
}
.alertWithData("Text Recognition Failed", data: $textRecognitionError) { _ in
Button("OK") {}
} message: { error in
Text(error.localizedDescription)
}
}
// TODO: attachments missing descriptions feature
private var thumbnailView: some View {
AttachmentThumbnailView(attachment: attachment)
.clipShape(RoundedRectangle(cornerRadius: 8))
.frame(width: thumbnailSize, height: thumbnailSize)
.contextMenu {
EditDrawingButton(attachment: attachment)
RecognizeTextButton(attachment: attachment, isRecognizingText: $isRecognizingText, error: $textRecognitionError)
DeleteButton(attachment: attachment)
} previewIfAvailable: {
// TODO: need to fix flash of preview changing size
AttachmentThumbnailView(attachment: attachment)
}
}
@ViewBuilder
private var descriptionView: some View {
if isRecognizingText {
ProgressView()
.progressViewStyle(.circular)
} else {
InlineAttachmentDescriptionView(attachment: attachment, minHeight: thumbnailSize)
}
}
}
private struct EditDrawingButton: View {
@ObservedObject var attachment: DraftAttachment
@Environment(\.composeUIConfig.presentDrawing) private var presentDrawing
var body: some View {
if attachment.drawingData != nil {
Button(action: editDrawing) {
Label("Edit Drawing", systemImage: "hand.draw")
}
}
}
private func editDrawing() {
if case .drawing(let drawing) = attachment.data {
presentDrawing?(drawing) {
attachment.drawing = $0
}
}
}
}
private struct RecognizeTextButton: View {
@ObservedObject var attachment: DraftAttachment
@Binding var isRecognizingText: Bool
@Binding var error: (any Error)?
@EnvironmentObject private var instanceFeatures: InstanceFeatures
var body: some View {
if attachment.type == .image {
Button {
Task {
await recognizeText()
}
} label: {
Label("Recognize Text", systemImage: "doc.text.viewfinder")
}
}
}
private func recognizeText() async {
isRecognizingText = true
defer { isRecognizingText = false }
do {
let data = try await getAttachmentData()
let observations = try await runRecognizeTextRequest(data: data)
if let observations {
var text = ""
for observation in observations {
let result = observation.topCandidates(1).first!
text.append(result.string)
text.append("\n")
}
self.attachment.attachmentDescription = text
}
} catch let error as NSError where error.domain == VNErrorDomain && error.code == 1 {
// The perform call throws an error with code 1 if the request is cancelled, which we don't want to show an alert for.
return
} catch {
self.error = error
}
}
private func getAttachmentData() async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
attachment.getData(features: instanceFeatures) { result in
switch result {
case .success(let (data, _)):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func runRecognizeTextRequest(data: Data) async throws -> [VNRecognizedTextObservation]? {
return try await withCheckedThrowingContinuation { continuation in
let handler = VNImageRequestHandler(data: data)
let request = VNRecognizeTextRequest { request, error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: request.results as? [VNRecognizedTextObservation])
}
}
request.recognitionLevel = .accurate
request.usesLanguageCorrection = true
DispatchQueue.global(qos: .userInitiated).async {
try? handler.perform([request])
}
}
}
}
private struct DeleteButton: View {
let attachment: DraftAttachment
var body: some View {
Button(role: .destructive, action: removeAttachment) {
Label("Delete", systemImage: "trash")
}
}
private func removeAttachment() {
let draft = attachment.draft
var array = draft.draftAttachments
guard let index = array.firstIndex(of: attachment) else {
return
}
array.remove(at: index)
draft.attachments = NSMutableOrderedSet(array: array)
}
}
//#Preview {
// AttachmentRowView()
//}

View File

@ -9,6 +9,7 @@ import SwiftUI
import PhotosUI
import PencilKit
import GalleryVC
import InstanceFeatures
struct AttachmentsSection: View {
@ObservedObject var draft: Draft
@ -24,6 +25,19 @@ struct AttachmentsSection: View {
// Add 4 to the minItemSize because otherwise drag-and-drop while reordering can alter the contentOffset by that much.
.frame(minHeight: 104)
}
static func insertAttachments(in draft: Draft, at index: Int, itemProviders: [NSItemProvider]) {
for provider in itemProviders where provider.canLoadObject(ofClass: DraftAttachment.self) {
provider.loadObject(ofClass: DraftAttachment.self) { object, error in
guard let attachment = object as? DraftAttachment else { return }
DispatchQueue.main.async {
DraftsPersistentContainer.shared.viewContext.insert(attachment)
attachment.draft = draft
draft.attachments.add(attachment)
}
}
}
}
}
// Use a UIViewControllerRepresentable so we have something from which to present the gallery VC.
@ -277,7 +291,7 @@ private struct AddAttachmentButton: View {
Button("Add photo or video", systemImage: "photo") {
presentAssetPicker {
let draft = viewController.draft!
AttachmentsListSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: $0.map(\.itemProvider))
AttachmentsSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: $0.map(\.itemProvider))
}
}
}
@ -308,3 +322,58 @@ private struct AddAttachmentButton: View {
.disabled(!enabled)
}
}
struct AddAttachmentConditionsModifier: ViewModifier {
@ObservedObject var draft: Draft
@EnvironmentObject private var instanceFeatures: InstanceFeatures
private var canAddAttachment: Bool {
if instanceFeatures.mastodonAttachmentRestrictions {
return draft.attachments.count < 4
&& draft.draftAttachments.allSatisfy { $0.type == .image }
&& draft.poll == nil
} else {
return true
}
}
func body(content: Content) -> some View {
content
.environment(\.canAddAttachment, canAddAttachment)
}
}
private struct CanAddAttachmentKey: EnvironmentKey {
static let defaultValue = false
}
extension EnvironmentValues {
var canAddAttachment: Bool {
get { self[CanAddAttachmentKey.self] }
set { self[CanAddAttachmentKey.self] = newValue }
}
}
struct DropAttachmentModifier: ViewModifier {
let draft: Draft
@Environment(\.canAddAttachment) private var canAddAttachment
func body(content: Content) -> some View {
content
.onDrop(of: DraftAttachment.readableTypeIdentifiersForItemProvider, delegate: AttachmentDropDelegate(draft: draft, canAddAttachment: canAddAttachment))
}
}
private struct AttachmentDropDelegate: DropDelegate {
let draft: Draft
let canAddAttachment: Bool
func validateDrop(info: DropInfo) -> Bool {
canAddAttachment
}
func performDrop(info: DropInfo) -> Bool {
AttachmentsSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: info.itemProviders(for: DraftAttachment.readableTypeIdentifiersForItemProvider))
return true
}
}

View File

@ -1,182 +0,0 @@
//
// AttachmentsListSection.swift
// ComposeUI
//
// Created by Shadowfacts on 10/14/24.
//
import SwiftUI
import InstanceFeatures
import PencilKit
struct AttachmentsListSection: View {
@ObservedObject var draft: Draft
@ObservedObject var instanceFeatures: InstanceFeatures
@Environment(\.canAddAttachment) private var canAddAttachment
var body: some View {
attachmentRows
buttons
.foregroundStyle(.tint)
#if os(visionOS)
.buttonStyle(.bordered)
.labelStyle(AttachmentButtonLabelStyle())
#endif
}
private var attachmentRows: some View {
ForEach(draft.draftAttachments) { attachment in
AttachmentRowView(attachment: attachment)
}
.onMove(perform: moveAttachments)
.onDelete(perform: deleteAttachments)
.onInsert(of: DraftAttachment.readableTypeIdentifiersForItemProvider) { offset, providers in
Self.insertAttachments(in: draft, at: offset, itemProviders: providers)
}
}
@ViewBuilder
private var buttons: some View {
AddPhotoButton(addAttachments: self.addAttachments)
AddDrawingButton(draft: draft)
TogglePollButton(draft: draft)
}
static func insertAttachments(in draft: Draft, at index: Int, itemProviders: [NSItemProvider]) {
for provider in itemProviders where provider.canLoadObject(ofClass: DraftAttachment.self) {
provider.loadObject(ofClass: DraftAttachment.self) { object, error in
guard let attachment = object as? DraftAttachment else { return }
DispatchQueue.main.async {
DraftsPersistentContainer.shared.viewContext.insert(attachment)
attachment.draft = draft
draft.attachments.add(attachment)
}
}
}
}
private func addAttachments(itemProviders: [NSItemProvider]) {
Self.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: itemProviders)
}
private func moveAttachments(from source: IndexSet, to destination: Int) {
// just using moveObjects(at:to:) on the draft.attachments NSMutableOrderedSet
// results in the order switching back to the previous order and then to the correct one
// on the subsequent 2 view updates. creating a new set with the proper order doesn't have that problem
var array = draft.draftAttachments
array.move(fromOffsets: source, toOffset: destination)
draft.attachments = NSMutableOrderedSet(array: array)
}
private func deleteAttachments(at indices: IndexSet) {
var array = draft.draftAttachments
array.remove(atOffsets: indices)
draft.attachments = NSMutableOrderedSet(array: array)
}
}
private struct AddPhotoButton: View {
let addAttachments: ([NSItemProvider]) -> Void
@Environment(\.composeUIConfig.presentAssetPicker) private var presentAssetPicker
var body: some View {
if let presentAssetPicker {
Button("Add photo or video", systemImage: "photo") {
presentAssetPicker { results in
addAttachments(results.map(\.itemProvider))
}
}
}
}
}
private struct AddDrawingButton: View {
let draft: Draft
@Environment(\.composeUIConfig.presentDrawing) private var presentDrawing
var body: some View {
if let presentDrawing {
Button("Add drawing", systemImage: "hand.draw") {
presentDrawing(PKDrawing()) { drawing in
let attachment = DraftAttachment(context: DraftsPersistentContainer.shared.viewContext)
attachment.id = UUID()
attachment.drawing = drawing
attachment.draft = self.draft
self.draft.attachments.add(attachment)
}
}
}
}
}
private struct TogglePollButton: View {
@ObservedObject var draft: Draft
var body: some View {
Button(draft.poll == nil ? "Add a poll" : "Remove poll", systemImage: "chart.bar.doc.horizontal") {
withAnimation {
draft.poll = draft.poll == nil ? Poll(context: DraftsPersistentContainer.shared.viewContext) : nil
}
}
.disabled(draft.attachments.count > 0)
}
}
struct AddAttachmentConditionsModifier: ViewModifier {
@ObservedObject var draft: Draft
@EnvironmentObject private var instanceFeatures: InstanceFeatures
private var canAddAttachment: Bool {
if instanceFeatures.mastodonAttachmentRestrictions {
return draft.attachments.count < 4
&& draft.draftAttachments.allSatisfy { $0.type == .image }
&& draft.poll == nil
} else {
return true
}
}
func body(content: Content) -> some View {
content
.environment(\.canAddAttachment, canAddAttachment)
}
}
private struct CanAddAttachmentKey: EnvironmentKey {
static let defaultValue = false
}
extension EnvironmentValues {
var canAddAttachment: Bool {
get { self[CanAddAttachmentKey.self] }
set { self[CanAddAttachmentKey.self] = newValue }
}
}
struct DropAttachmentModifier: ViewModifier {
let draft: Draft
@Environment(\.canAddAttachment) private var canAddAttachment
func body(content: Content) -> some View {
content
.onDrop(of: DraftAttachment.readableTypeIdentifiersForItemProvider, delegate: AttachmentDropDelegate(draft: draft, canAddAttachment: canAddAttachment))
}
}
private struct AttachmentDropDelegate: DropDelegate {
let draft: Draft
let canAddAttachment: Bool
func validateDrop(info: DropInfo) -> Bool {
canAddAttachment
}
func performDrop(info: DropInfo) -> Bool {
AttachmentsListSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: info.itemProviders(for: DraftAttachment.readableTypeIdentifiersForItemProvider))
return true
}
}

View File

@ -92,23 +92,10 @@ struct ComposeView: View {
ComposeDraftView(draft: draft, focusedField: $focusedField)
}
.padding(8)
// NewHeaderView(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
// .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
// .listRowSeparator(.hidden)
//
// ContentWarningTextField(draft: draft, focusedField: $focusedField)
// .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
// .listRowSeparator(.hidden)
//
// NewMainTextView(value: $draft.text, focusedField: $focusedField, handleAttachmentDrop: self.addAttachments)
// .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 4, trailing: 8))
// .listRowSeparator(.hidden)
//
// AttachmentsListSection(draft: draft, instanceFeatures: mastodonController.instanceFeatures)
}
private func addAttachments(_ itemProviders: [NSItemProvider]) {
AttachmentsListSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: itemProviders)
AttachmentsSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: itemProviders)
}
}

View File

@ -33,7 +33,7 @@ struct DraftContentEditor: View {
}
private func addAttachments(_ providers: [NSItemProvider]) {
AttachmentsListSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: providers)
AttachmentsSection.insertAttachments(in: draft, at: draft.attachments.count, itemProviders: providers)
}
}

View File

@ -29,19 +29,3 @@ struct HeaderView: View {
}.frame(height: 50)
}
}
struct NewHeaderView: View {
@ObservedObject var draft: Draft
@ObservedObject var instanceFeatures: InstanceFeatures
@Environment(\.currentAccount) private var currentAccount
private var charactersRemaining: Int {
let limit = instanceFeatures.maxStatusChars
let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0
return limit - (cwCount + CharacterCounter.count(text: draft.text, for: instanceFeatures))
}
var body: some View {
HeaderView(currentAccount: currentAccount, charsRemaining: charactersRemaining)
}
}