From a09d699249ceb7ef600c6fd7da66e18ef6603b31 Mon Sep 17 00:00:00 2001 From: Hailey Date: Sun, 16 Jun 2024 21:10:37 -0700 Subject: [PATCH] present app clip --- modules/BlueskyClip/AppDelegate.swift | 2 +- modules/BlueskyClip/ViewController.swift | 77 +++++++++++++++- .../StarterPack/StarterPackLandingScreen.tsx | 88 +++++++++++++++++-- 3 files changed, 154 insertions(+), 13 deletions(-) diff --git a/modules/BlueskyClip/AppDelegate.swift b/modules/BlueskyClip/AppDelegate.swift index a25fde64c9..879d9ede6a 100644 --- a/modules/BlueskyClip/AppDelegate.swift +++ b/modules/BlueskyClip/AppDelegate.swift @@ -10,7 +10,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow() - self.controller = ViewController() + self.controller = ViewController(window: self.window!) self.window?.rootViewController = self.controller self.window?.makeKeyAndVisible() diff --git a/modules/BlueskyClip/ViewController.swift b/modules/BlueskyClip/ViewController.swift index e8f77caa0c..737db71900 100644 --- a/modules/BlueskyClip/ViewController.swift +++ b/modules/BlueskyClip/ViewController.swift @@ -1,26 +1,95 @@ import UIKit import WebKit +import StoreKit -class ViewController: UIViewController { +class ViewController: UIViewController, WKScriptMessageHandler { + let defaults = UserDefaults(suiteName: "group.app.bsky") + + var window: UIWindow var webView: WKWebView? + var userContentController: WKUserContentController? + + 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 webView = WKWebView(frame: self.view.bounds) + + 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 - + 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, allowLossyConversion: false), + 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: "appClipStarterPackUri") + + case .store: + guard let keyToStoreAs = payload.keyToStoreAs, let jsonToStore = payload.jsonToStore else { + return + } + + self.defaults?.setValue(jsonToStore, forKey: keyToStoreAs) + } + } + func handleURL(url: URL) { + self.starterPackUrl = 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) + } +} + +struct WebViewActionPayload: Decodable { + enum Action: String, Decodable { + case present, store + } + + let action: Action + let keyToStoreAs: String? + let jsonToStore: String? } diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx index c79748f1ca..2b4b3bd290 100644 --- a/src/screens/StarterPack/StarterPackLandingScreen.tsx +++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx @@ -1,5 +1,6 @@ import React from 'react' -import {ScrollView, View} from 'react-native' +import {Pressable, ScrollView, View} from 'react-native' +import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -23,6 +24,8 @@ import {ListMaybePlaceholder} from '#/components/Lists' import {Default as ProfileCardInner} from '#/components/ProfileCard' import {Text} from '#/components/Typography' +const AnimatedPressable = Animated.createAnimatedComponent(Pressable) + function parseStarterPackHttpUri(uri: string): {name?: string; rkey?: string} { const parsed = new URL(uri) const [_, _path, name, rkey] = parsed.pathname.split('/') @@ -32,6 +35,16 @@ function parseStarterPackHttpUri(uri: string): {name?: string; rkey?: string} { } } +interface AppClipMessage { + action: 'present' | 'store' + keyToStoreAs?: string + jsonToStore?: string +} + +function postAppClipMessage(message: AppClipMessage) { + window.webkit.messageHandlers.onMessage.postMessage(JSON.stringify(message)) +} + export function LandingScreen({ setScreenState, }: { @@ -92,8 +105,26 @@ function LandingScreenInner({ const usedStarterPack = useUsedStarterPack() const {isTabletOrDesktop} = useWebMediaQueries() + const [appClipOverlayVisible, setAppClipOverlayVisible] = + React.useState(false) + const listItemsCount = starterPack.list?.listItemCount ?? 0 + const onJoinPress = () => { + if (usedStarterPack?.isClip) { + setAppClipOverlayVisible(true) + postAppClipMessage({ + action: 'present', + }) + } else { + setUsedStarterPack({ + uri: starterPack.uri, + cid: starterPack.cid, + }) + setScreenState(LoggedOutScreenState.S_CreateAccount) + } + } + if (!AppBskyGraphStarterpack.isRecord(record)) { return null } @@ -142,13 +173,7 @@ function LandingScreenInner({ ) : null}