Initial commit — iOS share extension for filing Gitea issues

Two-target Xcode project (xcodegen spec). The GiteaIssue container app
holds the base URL + personal access token in a shared keychain group;
the GiteaIssueShare extension reads them, surfaces a repo picker (with
recents) and a title/notes form, then creates the issue, uploads the
screenshot as an asset, and patches the body to embed it inline.

Min iOS 26.0, signing team V3PF3M6B6U.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-04-26 23:15:43 -05:00
commit 74e03c4e10
17 changed files with 1451 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import UIKit
import SwiftUI
import UniformTypeIdentifiers
final class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
Task { await loadAttachment() }
}
private func loadAttachment() async {
guard let item = (extensionContext?.inputItems as? [NSExtensionItem])?.first,
let provider = item.attachments?.first(where: { $0.hasItemConformingToTypeIdentifier(UTType.image.identifier) })
else {
present(host: ShareSheetView(image: nil, onClose: { [weak self] in self?.complete() }))
return
}
do {
let data = try await loadImageData(from: provider)
await MainActor.run {
let image = data.flatMap { UIImage(data: $0) }
let host = ShareSheetView(
image: image,
imageData: data,
onClose: { [weak self] in self?.complete() }
)
self.present(host: host)
}
} catch {
await MainActor.run {
present(host: ShareSheetView(image: nil, errorMessage: error.localizedDescription, onClose: { [weak self] in self?.complete() }))
}
}
}
private func loadImageData(from provider: NSItemProvider) async throws -> Data? {
try await withCheckedThrowingContinuation { continuation in
provider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { item, error in
if let error { continuation.resume(throwing: error); return }
if let data = item as? Data { continuation.resume(returning: data); return }
if let url = item as? URL, let data = try? Data(contentsOf: url) { continuation.resume(returning: data); return }
if let image = item as? UIImage, let data = image.pngData() { continuation.resume(returning: data); return }
continuation.resume(returning: nil)
}
}
}
private func present(host: ShareSheetView) {
let controller = UIHostingController(rootView: host)
controller.modalPresentationStyle = .formSheet
addChild(controller)
controller.view.frame = view.bounds
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(controller.view)
controller.didMove(toParent: self)
}
fileprivate func complete() {
extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
fileprivate func cancel() {
extensionContext?.cancelRequest(withError: NSError(domain: "GiteaIssueShare", code: 0))
}
}