// PasteSheet.swift // Confirm-before-paste sheet that previews clipboard content and lets // the user approve or cancel before sending a bracketed-paste frame. // // Privacy note: `UIPasteboard.general.string` is accessed lazily when the // sheet appears. iOS 16+ shows a system banner ("App pasted from …") but // does not require an explicit entitlement for this access pattern. import SwiftUI import UIKit // MARK: - PasteSheet /// A modal sheet that displays the current clipboard text and asks the user /// to confirm before sending it to the terminal as a `paste` frame. /// /// Dismiss flow: /// - **Paste** → encodes content as `{ type:"paste", data:"…" }` and calls /// `onSend`, then sets `isPresented` to `false`. /// - **Cancel** → sets `isPresented` to `false` with no send. @MainActor struct PasteSheet: View { // MARK: Bindings / callbacks @Binding var isPresented: Bool let onSend: (ClientToServer) -> Void // MARK: Private state /// The clipboard string captured when the view appears. @State private var clipboardContent: String? = nil // MARK: Body var body: some View { NavigationStack { Group { if let content = clipboardContent { if content.isEmpty { emptyClipboardView } else { previewView(content: content) } } else { emptyClipboardView } } .navigationTitle("Paste") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { isPresented = false } } if let content = clipboardContent, !content.isEmpty { ToolbarItem(placement: .confirmationAction) { Button("Paste") { onSend(.paste(data: content)) isPresented = false } .fontWeight(.semibold) } } } } .onAppear { // Capture clipboard when the sheet is presented. // Accessing on the main actor satisfies UIKit's thread requirement. clipboardContent = UIPasteboard.general.string } } // MARK: Sub-views /// Shown when the clipboard is nil or empty. private var emptyClipboardView: some View { VStack(spacing: 12) { Image(systemName: "clipboard") .font(.largeTitle) .foregroundStyle(.secondary) Text("Clipboard is empty") .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } /// Scrollable preview of the clipboard text with a bottom Paste button. private func previewView(content: String) -> some View { VStack(spacing: 0) { // Scrollable text preview ScrollView([.vertical, .horizontal]) { Text(content) .font(.system(size: 13, design: .monospaced)) .foregroundStyle(.primary) .frame(maxWidth: .infinity, alignment: .leading) .padding() .textSelection(.enabled) } .background(Color(uiColor: .systemGroupedBackground)) Divider() // Character count footer HStack { Label( "\(content.count) character\(content.count == 1 ? "" : "s")", systemImage: "text.cursor" ) .font(.caption) .foregroundStyle(.secondary) Spacer() } .padding(.horizontal) .padding(.vertical, 8) // Primary action button Button { onSend(.paste(data: content)) isPresented = false } label: { Label("Paste into Terminal", systemImage: "doc.on.clipboard") .font(.body.weight(.semibold)) .frame(maxWidth: .infinity) .padding(.vertical, 14) } .buttonStyle(.borderedProminent) .padding(.horizontal) .padding(.bottom, 16) } } }