Created
July 26, 2023 08:29
-
-
Save nixolas1/62f5ce8473224cc8437211e787489b1d to your computer and use it in GitHub Desktop.
React Native Carplay (2.1.0) plugin for Expo 47 (Dangerous modifications)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const {createRunOncePlugin, IOSConfig, withDangerousMod} = require("@expo/config-plugins") | |
const fs = require("fs/promises") | |
const withCarPlay = (config) => { | |
config = withCarPlayAppDelegate(config) | |
config = withCarPlayAppDelegateHeader(config) | |
return config | |
} | |
const withCarPlayAppDelegate = (conf) => { | |
return withDangerousMod(conf, [ | |
"ios", | |
async (config) => { | |
const fileInfo = IOSConfig.Paths.getAppDelegate(config.modRequest.projectRoot) | |
let contents = await fs.readFile(fileInfo.path, "utf-8") | |
if (fileInfo.language === "objcpp" || fileInfo.language === "objc") { | |
contents = modifySourceFile(contents) | |
} else { | |
throw new Error(`Cannot add CarPlay code to AppDelegate of language "${fileInfo.language}"`) | |
} | |
await fs.writeFile(fileInfo.path, contents) | |
return config | |
}, | |
]) | |
} | |
const withCarPlayAppDelegateHeader = (conf) => { | |
return withDangerousMod(conf, [ | |
"ios", | |
async (config) => { | |
const headerFilePath = IOSConfig.Paths.getAppDelegateHeaderFilePath(config.modRequest.projectRoot) | |
let contents = await fs.readFile(headerFilePath, "utf-8") | |
contents = modifyHeaderFile(contents) | |
await fs.writeFile(headerFilePath, contents) | |
return config | |
}, | |
]) | |
} | |
// @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate> | |
const newHeaders = ` | |
@property (nonatomic, strong) RCTBridge* bridge; | |
@end | |
@interface TemplateSceneDelegate: NSObject <CPTemplateApplicationSceneDelegate> | |
@end | |
@interface WindowSceneDelegate: EXAppDelegateWrapper <UIWindowSceneDelegate> | |
@end | |
` | |
const originalSceneRegex = /RCTBridge \*bridge =[\s\S]+?return YES;/ // todo: new arch | |
const originalSceneReplacement = ` | |
// Carplay removed stuff above, added below | |
self.bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions]; | |
self.window = nil; // Carplay added | |
[super application:application didFinishLaunchingWithOptions:launchOptions]; | |
return YES; | |
` | |
const sceneDelegateRegex = /@end[\s\n]+?$/ | |
const sceneDelegateReplacement = ` | |
@end | |
// Start carplay | |
#import <RNCarPlay.h> | |
@implementation TemplateSceneDelegate | |
- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene didConnectInterfaceController:(CPInterfaceController *)interfaceController toWindow:(CPWindow *)window { | |
[RNCarPlay connectWithInterfaceController:interfaceController window:window scene:templateApplicationScene]; | |
} | |
- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene didConnectInterfaceController:(CPInterfaceController *)interfaceController { | |
[RNCarPlay connectWithInterfaceController:interfaceController window:templateApplicationScene.carWindow scene:templateApplicationScene]; | |
} | |
- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene didDisconnectInterfaceController:(CPInterfaceController *)interfaceController { | |
[RNCarPlay disconnect]; | |
} | |
@end | |
@implementation WindowSceneDelegate | |
- (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity { | |
[RCTLinkingManager application:[UIApplication sharedApplication] openURL:userActivity.webpageURL options:@{}]; | |
} | |
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { | |
AppDelegate* appDelegate = (AppDelegate*)[UIApplication sharedApplication].delegate; | |
UIWindowScene* windowScene = (UIWindowScene*)scene; | |
for (NSUserActivity* activity in connectionOptions.userActivities) { | |
NSURL* url = activity.webpageURL; | |
if (url != nil) { | |
// Hack: Open URL after everything is initialized, after booting for 1.5s | |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ | |
[RCTLinkingManager application:[UIApplication sharedApplication] openURL:url options:@{}]; | |
}); | |
} | |
} | |
if ([[RNCPStore sharedManager] isConnected]) { | |
[appDelegate.bridge invalidate]; | |
appDelegate.bridge = [[RCTBridge alloc] initWithDelegate:appDelegate launchOptions:@{}]; | |
} | |
UIView *rootView = [self.reactDelegate createRootViewWithBridge:appDelegate.bridge moduleName:@"main" initialProperties:nil]; // updated | |
rootView.backgroundColor = [UIColor whiteColor]; | |
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; | |
[UIApplication sharedApplication].delegate.window = self.window; | |
self.window.windowScene = windowScene; | |
UIViewController *rootViewController = [self.reactDelegate createRootViewController]; | |
rootViewController.view = rootView; | |
self.window.rootViewController = rootViewController; | |
[self.window makeKeyAndVisible]; | |
[super application:[UIApplication sharedApplication] didFinishLaunchingWithOptions:nil]; | |
} | |
-(void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts { | |
UIOpenURLContext* context = [URLContexts anyObject]; | |
if (context != nil) { | |
[RCTLinkingManager application:[UIApplication sharedApplication] openURL:context.URL options:@{}]; | |
} | |
} | |
@end | |
` | |
const modifyHeaderFile = (contents) => { | |
contents = "#import <CarPlay/CarPlay.h>\n" + contents // add import carplay | |
contents = contents.replace(/@end/, newHeaders) // add bridge and scene delegate | |
return contents | |
} | |
const modifySourceFile = (contents) => { | |
contents = contents.replace(originalSceneRegex, originalSceneReplacement) | |
contents = contents.replace(sceneDelegateRegex, sceneDelegateReplacement) | |
return contents | |
} | |
const withCarPlayPlugin = (config) => { | |
config = withCarPlay(config) | |
// Return the modified config. | |
return config | |
} | |
const pkg = { | |
// Prevent this plugin from being run more than once. | |
name: "@valtyr/react-native-carplay", | |
// Indicates that this plugin is dangerously linked to a module, | |
// and might not work with the latest version of that module. | |
version: "UNVERSIONED", | |
} | |
module.exports = createRunOncePlugin(withCarPlayPlugin, pkg.name, pkg.version) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @nixolas1
have you got this working with Expo 49? In SDK 48 or 49 the
EXAppDelegateWrapper
has changed