feat(ios): share extension ile Ratebubble iOS istemcisini ekle ve paylaşım akışını düzelt

This commit is contained in:
2026-03-01 18:07:07 +03:00
parent 8c66fa9b82
commit 5c6a829a4d
20 changed files with 1235 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
import UIKit
import UniformTypeIdentifiers
final class ShareViewController: UIViewController {
private let statusLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
Task { await handleIncomingShare() }
}
private func setupUI() {
view.backgroundColor = .systemBackground
statusLabel.translatesAutoresizingMaskIntoConstraints = false
statusLabel.textAlignment = .center
statusLabel.numberOfLines = 0
statusLabel.text = "Paylaşılan bağlantı alınıyor..."
view.addSubview(statusLabel)
NSLayoutConstraint.activate([
statusLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
statusLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
statusLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
@MainActor
private func updateStatus(_ text: String) {
statusLabel.text = text
}
private func handleIncomingShare() async {
guard let item = extensionContext?.inputItems.first as? NSExtensionItem,
let providers = item.attachments else {
updateStatus("Paylaşılan içerik okunamadı.")
return
}
for provider in providers {
if let extracted = await extractURL(from: provider), isSupportedStreamingURL(extracted) {
SharedPayloadStore.saveIncomingURL(extracted.absoluteString)
updateStatus("Bağlantı alındı, uygulama açılıyor...")
openHostApp()
return
}
}
updateStatus("Geçerli bir Netflix/Prime Video linki bulunamadı.")
}
private func extractURL(from provider: NSItemProvider) async -> URL? {
if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
return await withCheckedContinuation { continuation in
provider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { item, _ in
continuation.resume(returning: item as? URL)
}
}
}
if provider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
return await withCheckedContinuation { continuation in
provider.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { item, _ in
if let raw = item as? String, let url = URL(string: raw.trimmingCharacters(in: .whitespacesAndNewlines)) {
continuation.resume(returning: url)
return
}
continuation.resume(returning: nil)
}
}
}
return nil
}
private func isSupportedStreamingURL(_ url: URL) -> Bool {
let host = url.host?.lowercased() ?? ""
let netflixHosts = ["www.netflix.com", "netflix.com", "www.netflix.com.tr", "netflix.com.tr"]
let primeHosts = ["www.primevideo.com", "primevideo.com", "www.amazon.com", "amazon.com"]
let isNetflix = netflixHosts.contains(host)
let isPrime = primeHosts.contains(host)
guard isNetflix || isPrime else { return false }
let path = url.path.lowercased()
if path.contains("/title/") || path.contains("/watch/") || path.contains("/detail/") {
return true
}
// Some share links can be shortened/redirect style without a canonical path.
return !path.isEmpty && path != "/"
}
private func openHostApp() {
guard let url = URL(string: "\(SharedConfig.appURLScheme)://ingest") else {
extensionContext?.completeRequest(returningItems: nil)
return
}
extensionContext?.open(url) { success in
// If opening succeeded, the system should transition to the host app.
// Completing the extension request immediately can bounce back to the source app.
guard !success else { return }
self.extensionContext?.completeRequest(returningItems: nil)
}
}
}