diff options
author | Hailey <me@haileyok.com> | 2024-06-21 21:38:04 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-21 21:38:04 -0700 |
commit | f089f4578131e83cd177b7809ce0f7b75779dfdc (patch) | |
tree | 51978aede2040fb8dc319f0749d3de77c7811fbe /modules/BlueskyClip | |
parent | 35f64535cb8dfa0fe46e740a6398f3b991ecfbc7 (diff) | |
download | voidsky-f089f4578131e83cd177b7809ce0f7b75779dfdc.tar.zst |
Starter Packs (#4332)
Co-authored-by: Dan Abramov <dan.abramov@gmail.com> Co-authored-by: Paul Frazee <pfrazee@gmail.com> Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'modules/BlueskyClip')
-rw-r--r-- | modules/BlueskyClip/AppDelegate.swift | 32 | ||||
-rw-r--r-- | modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png | bin | 0 -> 473960 bytes | |||
-rw-r--r-- | modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json | 14 | ||||
-rw-r--r-- | modules/BlueskyClip/Images.xcassets/Contents.json | 6 | ||||
-rw-r--r-- | modules/BlueskyClip/ViewController.swift | 133 |
5 files changed, 185 insertions, 0 deletions
diff --git a/modules/BlueskyClip/AppDelegate.swift b/modules/BlueskyClip/AppDelegate.swift new file mode 100644 index 000000000..684194953 --- /dev/null +++ b/modules/BlueskyClip/AppDelegate.swift @@ -0,0 +1,32 @@ +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + var controller: ViewController? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let window = UIWindow() + self.window = UIWindow() + + let controller = ViewController(window: window) + self.controller = controller + + window.rootViewController = self.controller + window.makeKeyAndVisible() + + return true + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + self.controller?.handleURL(url: url) + return true + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if let incomingURL = userActivity.webpageURL { + self.controller?.handleURL(url: incomingURL) + } + return true + } +} diff --git a/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png new file mode 100644 index 000000000..75ce4b813 --- /dev/null +++ b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png Binary files differdiff --git a/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..c3bb428db --- /dev/null +++ b/modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "App-Icon-1024x1024@1x.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/modules/BlueskyClip/Images.xcassets/Contents.json b/modules/BlueskyClip/Images.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/modules/BlueskyClip/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/modules/BlueskyClip/ViewController.swift b/modules/BlueskyClip/ViewController.swift new file mode 100644 index 000000000..b178644b8 --- /dev/null +++ b/modules/BlueskyClip/ViewController.swift @@ -0,0 +1,133 @@ +import UIKit +import WebKit +import StoreKit + +class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate { + let defaults = UserDefaults(suiteName: "group.app.bsky") + + var window: UIWindow + var webView: WKWebView? + + var prevUrl: URL? + var starterPackUrl: URL? + + init(window: UIWindow) { + self.window = window + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let contentController = WKUserContentController() + contentController.add(self, name: "onMessage") + let configuration = WKWebViewConfiguration() + configuration.userContentController = contentController + + let webView = WKWebView(frame: self.view.bounds, configuration: configuration) + webView.translatesAutoresizingMaskIntoConstraints = false + webView.contentMode = .scaleToFill + webView.navigationDelegate = self + self.view.addSubview(webView) + self.webView = webView + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard let response = message.body as? String, + let data = response.data(using: .utf8), + let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else { + return + } + + switch payload.action { + case .present: + guard let url = self.starterPackUrl else { + return + } + + self.presentAppStoreOverlay() + defaults?.setValue(url.absoluteString, forKey: "starterPackUri") + + case .store: + guard let keyToStoreAs = payload.keyToStoreAs, let jsonToStore = payload.jsonToStore else { + return + } + + self.defaults?.setValue(jsonToStore, forKey: keyToStoreAs) + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + // Detect when we land on the right URL. This is incase of a short link opening the app clip + guard let url = navigationAction.request.url else { + return .allow + } + + // Store the previous one to compare later, but only set starterPackUrl when we find the right one + prevUrl = url + // pathComponents starts with "/" as the first component, then each path name. so... + // ["/", "start", "name", "rkey"] + if url.pathComponents.count == 4, + url.pathComponents[1] == "start" { + self.starterPackUrl = url + } + + return .allow + } + + func handleURL(url: URL) { + let urlString = "\(url.absoluteString)?clip=true" + if let url = URL(string: urlString) { + self.webView?.load(URLRequest(url: url)) + } + } + + func presentAppStoreOverlay() { + guard let windowScene = self.window.windowScene else { + return + } + + let configuration = SKOverlay.AppClipConfiguration(position: .bottomRaised) + let overlay = SKOverlay(configuration: configuration) + + overlay.present(in: windowScene) + } + + func getHost(_ url: URL?) -> String? { + if #available(iOS 16.0, *) { + return url?.host() + } else { + return url?.host + } + } + + func getQuery(_ url: URL?) -> String? { + if #available(iOS 16.0, *) { + return url?.query() + } else { + return url?.query + } + } + + func urlMatchesPrevious(_ url: URL?) -> Bool { + if #available(iOS 16.0, *) { + return url?.query() == prevUrl?.query() && url?.host() == prevUrl?.host() && url?.query() == prevUrl?.query() + } else { + return url?.query == prevUrl?.query && url?.host == prevUrl?.host && url?.query == prevUrl?.query + } + } +} + +struct WebViewActionPayload: Decodable { + enum Action: String, Decodable { + case present, store + } + + let action: Action + let keyToStoreAs: String? + let jsonToStore: String? +} |