mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Atish Patel <atish@squareup.com> Co-authored-by: Codex <noreply@openai.com>
204 lines
5.9 KiB
Swift
204 lines
5.9 KiB
Swift
import AVFoundation
|
|
import Flutter
|
|
import UIKit
|
|
import UserNotifications
|
|
|
|
@main
|
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
|
private var mediaUploadChannel: FlutterMethodChannel?
|
|
|
|
override func application(
|
|
_ application: UIApplication,
|
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
) -> Bool {
|
|
UNUserNotificationCenter.current().requestAuthorization(options: [.badge]) { _, _ in }
|
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
|
}
|
|
|
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
|
mediaUploadChannel = FlutterMethodChannel(
|
|
name: "buzz/media_upload",
|
|
binaryMessenger: engineBridge.applicationRegistrar.messenger()
|
|
)
|
|
mediaUploadChannel?.setMethodCallHandler { [weak self] call, result in
|
|
self?.handleMediaUploadMethodCall(call, result: result)
|
|
}
|
|
}
|
|
|
|
private func handleMediaUploadMethodCall(
|
|
_ call: FlutterMethodCall,
|
|
result: @escaping FlutterResult
|
|
) {
|
|
switch call.method {
|
|
case "sanitizeImageForUpload":
|
|
guard
|
|
let arguments = call.arguments as? [String: Any],
|
|
let typedData = arguments["bytes"] as? FlutterStandardTypedData,
|
|
let mimeType = arguments["mimeType"] as? String
|
|
else {
|
|
result(
|
|
FlutterError(
|
|
code: "invalid_arguments",
|
|
message: "Expected image bytes and mime type.",
|
|
details: nil
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
guard let image = UIImage(data: typedData.data) else {
|
|
result(
|
|
FlutterError(
|
|
code: "sanitize_failed",
|
|
message: "Unable to decode picked image.",
|
|
details: nil
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
let sanitizedData: Data?
|
|
switch mimeType {
|
|
case "image/png":
|
|
sanitizedData = image.pngData()
|
|
case "image/jpeg":
|
|
sanitizedData = image.jpegData(compressionQuality: 1.0)
|
|
case "image/webp":
|
|
sanitizedData = image.pngData()
|
|
default:
|
|
sanitizedData = nil
|
|
}
|
|
|
|
guard let sanitizedData else {
|
|
result(
|
|
FlutterError(
|
|
code: "sanitize_failed",
|
|
message: "Unable to sanitize picked image.",
|
|
details: mimeType
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
result(FlutterStandardTypedData(bytes: sanitizedData))
|
|
case "transcodeImageToJpeg":
|
|
guard let typedData = call.arguments as? FlutterStandardTypedData else {
|
|
result(
|
|
FlutterError(
|
|
code: "invalid_arguments",
|
|
message: "Expected raw image bytes.",
|
|
details: nil
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
guard let image = UIImage(data: typedData.data),
|
|
let jpegData = image.jpegData(compressionQuality: 1.0)
|
|
else {
|
|
result(
|
|
FlutterError(
|
|
code: "transcode_failed",
|
|
message: "Unable to convert picked image to JPEG.",
|
|
details: nil
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
result(FlutterStandardTypedData(bytes: jpegData))
|
|
case "transcodeVideoToMp4":
|
|
guard let sourcePath = call.arguments as? String else {
|
|
result(
|
|
FlutterError(
|
|
code: "invalid_arguments",
|
|
message: "Expected source file path as String.",
|
|
details: nil
|
|
)
|
|
)
|
|
return
|
|
}
|
|
transcodeVideoToMp4(sourcePath: sourcePath, result: result)
|
|
case "clipboardHasImage":
|
|
result(UIPasteboard.general.hasImages)
|
|
case "readClipboardImage":
|
|
guard let imageData = Self.clipboardImageData(from: UIPasteboard.general) else {
|
|
result(nil)
|
|
return
|
|
}
|
|
result(FlutterStandardTypedData(bytes: imageData))
|
|
default:
|
|
result(FlutterMethodNotImplemented)
|
|
}
|
|
}
|
|
|
|
static func clipboardImageData(from pasteboard: UIPasteboard) -> Data? {
|
|
if let pngData = pasteboard.data(forPasteboardType: "public.png") {
|
|
return pngData
|
|
}
|
|
if let jpegData = pasteboard.data(forPasteboardType: "public.jpeg") {
|
|
return jpegData
|
|
}
|
|
for imageType in ["public.heic", "public.heif", "org.webmproject.webp", "com.compuserve.gif"] {
|
|
if let imageData = pasteboard.data(forPasteboardType: imageType) {
|
|
return imageData
|
|
}
|
|
}
|
|
guard let image = pasteboard.image else {
|
|
return nil
|
|
}
|
|
return image.pngData()
|
|
}
|
|
|
|
private func transcodeVideoToMp4(
|
|
sourcePath: String,
|
|
result: @escaping FlutterResult
|
|
) {
|
|
let sourceURL = URL(fileURLWithPath: sourcePath)
|
|
let asset = AVURLAsset(url: sourceURL)
|
|
|
|
guard let exportSession = AVAssetExportSession(
|
|
asset: asset,
|
|
presetName: AVAssetExportPresetPassthrough
|
|
) else {
|
|
result(
|
|
FlutterError(
|
|
code: "transcode_failed",
|
|
message: "Unable to create export session.",
|
|
details: nil
|
|
)
|
|
)
|
|
return
|
|
}
|
|
|
|
let outputURL = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent(UUID().uuidString)
|
|
.appendingPathExtension("mp4")
|
|
|
|
exportSession.outputURL = outputURL
|
|
exportSession.outputFileType = .mp4
|
|
exportSession.shouldOptimizeForNetworkUse = true
|
|
exportSession.metadataItemFilter = AVMetadataItemFilter.forSharing()
|
|
|
|
exportSession.exportAsynchronously {
|
|
switch exportSession.status {
|
|
case .completed:
|
|
result(outputURL.path)
|
|
default:
|
|
let errorMessage = exportSession.error?.localizedDescription
|
|
?? "Video transcoding failed with status \(exportSession.status.rawValue)."
|
|
result(
|
|
FlutterError(
|
|
code: "transcode_failed",
|
|
message: errorMessage,
|
|
details: nil
|
|
)
|
|
)
|
|
// Clean up partial output on failure.
|
|
try? FileManager.default.removeItem(at: outputURL)
|
|
}
|
|
}
|
|
}
|
|
}
|