Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
120
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 160 lines 4.5 kB view raw
1import UIKit 2import WebKit 3import StoreKit 4 5class ViewController: UIViewController, WKScriptMessageHandler, WKNavigationDelegate { 6 let defaults = UserDefaults(suiteName: "group.app.witchsky") 7 8 var window: UIWindow 9 var webView: WKWebView? 10 11 var prevUrl: URL? 12 var starterPackUrl: URL? 13 14 init(window: UIWindow) { 15 self.window = window 16 super.init(nibName: nil, bundle: nil) 17 } 18 19 required init?(coder: NSCoder) { 20 fatalError("init(coder:) has not been implemented") 21 } 22 23 override func viewDidLoad() { 24 super.viewDidLoad() 25 26 let contentController = WKUserContentController() 27 contentController.add(self, name: "onMessage") 28 let configuration = WKWebViewConfiguration() 29 configuration.userContentController = contentController 30 31 let webView = WKWebView(frame: self.view.bounds, configuration: configuration) 32 webView.translatesAutoresizingMaskIntoConstraints = false 33 webView.contentMode = .scaleToFill 34 webView.navigationDelegate = self 35 self.view.addSubview(webView) 36 self.webView = webView 37 self.webView?.load(URLRequest(url: URL(string: "https://bsky.app/?splash=true&clip=true")!)) 38 } 39 40 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 41 guard let response = message.body as? String, 42 let data = response.data(using: .utf8), 43 let payload = try? JSONDecoder().decode(WebViewActionPayload.self, from: data) else { 44 return 45 } 46 47 switch payload.action { 48 case .present: 49 self.presentAppStoreOverlay() 50 51 if let url = self.starterPackUrl { 52 defaults?.setValue(url.absoluteString, forKey: "starterPackUri") 53 } 54 case .store: 55 guard let keyToStoreAs = payload.keyToStoreAs, let jsonToStore = payload.jsonToStore else { 56 return 57 } 58 59 self.defaults?.setValue(jsonToStore, forKey: keyToStoreAs) 60 } 61 } 62 63 func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { 64 // Detect when we land on the right URL. This is incase of a short link opening the app clip 65 guard let url = navigationAction.request.url else { 66 return .allow 67 } 68 69 // Store the previous one to compare later, but only set starterPackUrl when we find the right one 70 prevUrl = url 71 // pathComponents starts with "/" as the first component, then each path name. so... 72 // ["/", "start", "name", "rkey"] 73 if isStarterPackUrl(url) { 74 self.starterPackUrl = url 75 } 76 77 return .allow 78 } 79 80 func isStarterPackUrl(_ url: URL) -> Bool { 81 var host: String? 82 if #available(iOS 16.0, *) { 83 host = url.host() 84 } else { 85 host = url.host 86 } 87 88 switch host { 89 case "bsky.app": 90 if url.pathComponents.count == 4, 91 url.pathComponents[1] == "start" || url.pathComponents[1] == "starter-pack" { 92 return true 93 } 94 return false 95 case "go.bsky.app": 96 if url.pathComponents.count == 2 { 97 return true 98 } 99 return false 100 default: 101 return false 102 } 103 } 104 105 func handleURL(url: URL) { 106 if isStarterPackUrl(url) { 107 let urlString = "\(url.absoluteString)?clip=true" 108 if let url = URL(string: urlString) { 109 self.webView?.load(URLRequest(url: url)) 110 } 111 } else { 112 self.webView?.load(URLRequest(url: URL(string: "https://bsky.app/?splash=true&clip=true")!)) 113 } 114 } 115 116 func presentAppStoreOverlay() { 117 guard let windowScene = self.window.windowScene else { 118 return 119 } 120 121 let configuration = SKOverlay.AppClipConfiguration(position: .bottomRaised) 122 let overlay = SKOverlay(configuration: configuration) 123 124 overlay.present(in: windowScene) 125 } 126 127 func getHost(_ url: URL?) -> String? { 128 if #available(iOS 16.0, *) { 129 return url?.host() 130 } else { 131 return url?.host 132 } 133 } 134 135 func getQuery(_ url: URL?) -> String? { 136 if #available(iOS 16.0, *) { 137 return url?.query() 138 } else { 139 return url?.query 140 } 141 } 142 143 func urlMatchesPrevious(_ url: URL?) -> Bool { 144 if #available(iOS 16.0, *) { 145 return url?.query() == prevUrl?.query() && url?.host() == prevUrl?.host() && url?.query() == prevUrl?.query() 146 } else { 147 return url?.query == prevUrl?.query && url?.host == prevUrl?.host && url?.query == prevUrl?.query 148 } 149 } 150} 151 152struct WebViewActionPayload: Decodable { 153 enum Action: String, Decodable { 154 case present, store 155 } 156 157 let action: Action 158 let keyToStoreAs: String? 159 let jsonToStore: String? 160}