// // DraftsController.swift // ComposeUI // // Created by Shadowfacts on 3/7/23. // import SwiftUI import TuskerComponents class DraftsController: ViewController { unowned let parent: ComposeController @Binding var isPresented: Bool @Published var draftForDifferentReply: Draft? init(parent: ComposeController, isPresented: Binding) { self.parent = parent self._isPresented = isPresented } var view: some View { DraftsRepresentable() } func maybeSelectDraft(_ draft: Draft) { if draft.inReplyToID != parent.draft.inReplyToID, parent.draft.hasContent { draftForDifferentReply = draft } else { confirmSelectDraft(draft) } } func cancelSelectingDraft() { draftForDifferentReply = nil } func confirmSelectDraft(_ draft: Draft) { parent.selectDraft(draft) closeDrafts() } func deleteDraft(_ draft: Draft) { DraftsPersistentContainer.shared.viewContext.delete(draft) } func closeDrafts() { isPresented = false DraftsPersistentContainer.shared.save() } struct DraftsRepresentable: UIViewControllerRepresentable { typealias UIViewControllerType = UIHostingController func makeUIViewController(context: Context) -> UIHostingController { return UIHostingController(rootView: DraftsView()) } func updateUIViewController(_ uiViewController: UIHostingController, context: Context) { } } struct DraftsView: View { @EnvironmentObject private var controller: DraftsController @EnvironmentObject private var currentDraft: Draft @FetchRequest(sortDescriptors: [SortDescriptor(\Draft.lastModified, order: .reverse)]) private var drafts: FetchedResults var body: some View { NavigationView { List { ForEach(drafts) { draft in Button(action: { controller.maybeSelectDraft(draft) }) { DraftRow(draft: draft) } .contextMenu { Button(role: .destructive, action: { controller.deleteDraft(draft) }) { Label("Delete Draft", systemImage: "trash") } } .ifLet(controller.parent.config.userActivityForDraft(draft), modify: { view, activity in view.onDrag { activity } }) } .onDelete { indices in indices.map { drafts[$0] }.forEach(controller.deleteDraft) } } .listStyle(.plain) .navigationTitle("Drafts") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { cancelButton } } } .alertWithData("Different Reply", data: $controller.draftForDifferentReply) { draft in Button(role: .cancel, action: controller.cancelSelectingDraft) { Text("Cancel") } Button(action: { controller.confirmSelectDraft(draft) }) { Text("Restore Draft") } } message: { _ in Text("The selected draft is a reply to a different post, do you wish to use it?") } .onAppear { drafts.nsPredicate = NSPredicate(format: "accountID == %@ AND id != %@ AND lastModified != nil", controller.parent.mastodonController.accountInfo!.id, currentDraft.id as NSUUID) } } private var cancelButton: some View { Button(action: controller.closeDrafts) { Text("Cancel") } } } } private struct DraftRow: View { @ObservedObject var draft: Draft @EnvironmentObject private var controller: DraftsController var body: some View { HStack { VStack(alignment: .leading) { if draft.editedStatusID != nil { // shouldn't happen unless the app crashed/was killed during an edit Text("Edit") .font(.body.bold()) .foregroundColor(.orange) } if draft.contentWarningEnabled { Text(draft.contentWarning) .font(.body.bold()) .foregroundColor(.secondary) } Text(draft.text) .font(.body) HStack(spacing: 8) { ForEach(draft.draftAttachments) { attachment in ControllerView(controller: { AttachmentThumbnailController(attachment: attachment, parent: controller.parent) }) .aspectRatio(contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 5)) .frame(height: 50) } } } Spacer() Text(draft.lastModified.formatted(.abbreviatedTimeAgo)) .font(.body) .foregroundColor(.secondary) } } } private extension View { @ViewBuilder func ifLet(_ value: T?, modify: (Self, T) -> V) -> some View { if let value { modify(self, value) } else { self } } }