134 lines
4.6 KiB
Swift
134 lines
4.6 KiB
Swift
import Flutter
|
|
import UIKit
|
|
import AVFoundation
|
|
|
|
private final class ThumbnailResponseGate {
|
|
private let lock = NSLock()
|
|
private var completed = false
|
|
|
|
func send(_ result: @escaping FlutterResult, data: Data?) {
|
|
lock.lock()
|
|
if completed {
|
|
lock.unlock()
|
|
return
|
|
}
|
|
completed = true
|
|
lock.unlock()
|
|
DispatchQueue.main.async {
|
|
result(data)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Keeps the system LaunchScreen as the only splash. Flutter would otherwise
|
|
/// instantiate that storyboard again and the icon appears a second time.
|
|
final class RunnerFlutterViewController: FlutterViewController {
|
|
override func loadDefaultSplashScreenView() -> Bool {
|
|
false
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = UIColor(red: 16.0 / 255.0, green: 12.0 / 255.0, blue: 10.0 / 255.0, alpha: 1)
|
|
}
|
|
}
|
|
|
|
@main
|
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
|
private let thumbnailQueue = DispatchQueue(
|
|
label: "com.stormg.lumadrama.thumbnail",
|
|
qos: .userInitiated
|
|
)
|
|
|
|
override func application(
|
|
_ application: UIApplication,
|
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
) -> Bool {
|
|
UIApplication.shared.isIdleTimerDisabled = true
|
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
|
}
|
|
|
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
|
let channel = FlutterMethodChannel(
|
|
name: "com.stormg.lumadrama/share",
|
|
binaryMessenger: engineBridge.applicationRegistrar.messenger()
|
|
)
|
|
channel.setMethodCallHandler { [weak self] call, result in
|
|
guard call.method == "shareText",
|
|
let arguments = call.arguments as? [String: Any],
|
|
let text = arguments["text"] as? String,
|
|
!text.isEmpty,
|
|
let controller = self?.window?.rootViewController else {
|
|
result(FlutterError(code: "share_unavailable", message: "Share sheet is unavailable", details: nil))
|
|
return
|
|
}
|
|
let activity = UIActivityViewController(activityItems: [text], applicationActivities: nil)
|
|
activity.popoverPresentationController?.sourceView = controller.view
|
|
activity.completionWithItemsHandler = { _, _, _, _ in result(nil) }
|
|
controller.present(activity, animated: true)
|
|
}
|
|
|
|
let thumbnailChannel = FlutterMethodChannel(
|
|
name: "com.stormg.lumadrama/thumbnail",
|
|
binaryMessenger: engineBridge.applicationRegistrar.messenger()
|
|
)
|
|
thumbnailChannel.setMethodCallHandler { [weak self] call, result in
|
|
guard call.method == "frame" else {
|
|
result(FlutterMethodNotImplemented)
|
|
return
|
|
}
|
|
guard let self,
|
|
let arguments = call.arguments as? [String: Any],
|
|
let timeNumber = arguments["timeMs"] as? NSNumber else {
|
|
result(nil)
|
|
return
|
|
}
|
|
let localPath = arguments["localPath"] as? String ?? ""
|
|
let remoteUrl = arguments["url"] as? String ?? ""
|
|
let headers = arguments["headers"] as? [String: String] ?? [:]
|
|
let assetUrl: URL?
|
|
if !localPath.isEmpty {
|
|
assetUrl = localPath.hasPrefix("file:")
|
|
? URL(string: localPath)
|
|
: URL(fileURLWithPath: localPath)
|
|
} else if !remoteUrl.isEmpty {
|
|
assetUrl = URL(string: remoteUrl)
|
|
} else {
|
|
assetUrl = nil
|
|
}
|
|
guard let assetUrl else {
|
|
result(nil)
|
|
return
|
|
}
|
|
|
|
let options: [String: Any]? = headers.isEmpty
|
|
? nil
|
|
: [AVURLAssetHTTPHeaderFieldsKey: headers]
|
|
let asset = AVURLAsset(url: assetUrl, options: options)
|
|
let generator = AVAssetImageGenerator(asset: asset)
|
|
generator.appliesPreferredTrackTransform = true
|
|
generator.maximumSize = CGSize(width: 240, height: 426)
|
|
generator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
|
|
generator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)
|
|
let time = CMTime(value: timeNumber.int64Value, timescale: 1000)
|
|
let gate = ThumbnailResponseGate()
|
|
|
|
self.thumbnailQueue.async {
|
|
do {
|
|
let image = try generator.copyCGImage(at: time, actualTime: nil)
|
|
let data = UIImage(cgImage: image).jpegData(compressionQuality: 0.7)
|
|
gate.send(result, data: data)
|
|
} catch {
|
|
gate.send(result, data: nil)
|
|
}
|
|
}
|
|
DispatchQueue.global(qos: .userInitiated).asyncAfter(
|
|
deadline: .now() + 1.5
|
|
) {
|
|
gate.send(result, data: nil)
|
|
}
|
|
}
|
|
}
|
|
}
|