Skip to content

Commit

Permalink
Merge pull request #129 from hugeBlack/main
Browse files Browse the repository at this point in the history
Allow "Open In App" for Apps inside LiveContainer & Remove Data Folder after Uninstalling
  • Loading branch information
khanhduytran0 authored Aug 30, 2024
2 parents fd71dd7 + 65557d2 commit 10a49ba
Show file tree
Hide file tree
Showing 15 changed files with 437 additions and 18 deletions.
2 changes: 1 addition & 1 deletion LCSharedUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
+ (NSString *)certificatePassword;
+ (BOOL)launchToGuestApp;
+ (BOOL)launchToGuestAppWithURL:(NSURL *)url;

+ (void)setWebPageUrlForNextLaunch:(NSString*)urlString;
@end
4 changes: 4 additions & 0 deletions LCSharedUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url {
return NO;
}

+ (void)setWebPageUrlForNextLaunch:(NSString*) urlString {
[lcUserDefaults setObject:urlString forKey:@"webPageToOpen"];
}

@end
12 changes: 12 additions & 0 deletions LiveContainerUI/LCAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#import "LCJITLessSetupViewController.h"
#import "LCTabBarController.h"
#import "LCUtils.h"
#import <UIKit/UIKit.h>

@implementation LCAppDelegate

Expand All @@ -20,6 +21,17 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
// handle page open request from URL scheme
if([url.host isEqualToString:@"open-web-page"]) {
NSURLComponents* urlComponent = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
if(urlComponent.queryItems.count == 0){
return YES;
}

NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:urlComponent.queryItems[0].value options:0];
NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
[((LCTabBarController*)_rootViewController) openWebPage:decodedUrl];
}
return [LCUtils launchToGuestAppWithURL:url];
}

Expand Down
5 changes: 3 additions & 2 deletions LiveContainerUI/LCAppInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
NSMutableDictionary* _info;
NSString* _bundlePath;
}

@property NSString* relativeBundlePath;
- (NSMutableDictionary*)info;
- (UIImage*)icon;
- (NSString*)displayName;
Expand All @@ -14,9 +14,10 @@
- (NSString*)version;
- (NSString*)dataUUID;
- (NSString*)tweakFolder;
- (NSMutableArray*) urlSchemes;
- (void)setDataUUID:(NSString *)uuid;
- (void)setTweakFolder:(NSString *)tweakFolder;
- (instancetype)initWithBundlePath:(NSString*)bundlePath;
- (NSDictionary *)generateWebClipConfig;
- (void)save;
@end
@end
27 changes: 26 additions & 1 deletion LiveContainerUI/LCAppInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,38 @@
@implementation LCAppInfo
- (instancetype)initWithBundlePath:(NSString*)bundlePath {
self = [super init];

if(self) {
_bundlePath = bundlePath;
_info = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath]];

}
return self;
}

- (NSMutableArray*)urlSchemes {
// find all url schemes
NSMutableArray* urlSchemes = [[NSMutableArray alloc] init];
int nowSchemeCount = 0;
if (_info[@"CFBundleURLTypes"]) {
NSMutableArray* urlTypes = _info[@"CFBundleURLTypes"];

for(int i = 0; i < [urlTypes count]; ++i) {
NSMutableDictionary* nowUrlType = [urlTypes objectAtIndex:i];
if (!nowUrlType[@"CFBundleURLSchemes"]){
continue;
}
NSMutableArray *schemes = nowUrlType[@"CFBundleURLSchemes"];
for(int j = 0; j < [schemes count]; ++j) {
[urlSchemes insertObject:[schemes objectAtIndex:j] atIndex:nowSchemeCount];
++nowSchemeCount;
}
}
}

return urlSchemes;
}

- (NSString*)displayName {
if (_info[@"CFBundleDisplayName"]) {
return _info[@"CFBundleDisplayName"];
Expand Down Expand Up @@ -127,4 +152,4 @@ - (NSDictionary *)generateWebClipConfig {
- (void)save {
[_info writeToFile:[NSString stringWithFormat:@"%@/Info.plist", _bundlePath] atomically:YES];
}
@end
@end
1 change: 1 addition & 0 deletions LiveContainerUI/LCAppListViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

@interface LCAppListViewController : UITableViewController <UIDocumentPickerDelegate>
@property(nonatomic) NSString* acError;
- (void) openWebViewByURLString:(NSString*) urlString;
@end
118 changes: 117 additions & 1 deletion LiveContainerUI/LCAppListViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "UIKitPrivate.h"
#import "UIViewController+LCAlert.h"
#import "unarchive.h"
#import "LCWebView.h"

@implementation NSURL(hack)
- (BOOL)safari_isHTTPFamilyURL {
Expand All @@ -22,8 +23,9 @@ - (BOOL)safari_isHTTPFamilyURL {
@interface LCAppListViewController ()
@property(atomic) NSMutableArray<NSString *> *objects;
@property(nonatomic) NSString *bundlePath, *docPath, *tweakPath;

@property(atomic) NSString* pageUrlToOpen;
@property(nonatomic) MBRoundProgressView *progressView;

@end

@implementation LCAppListViewController
Expand Down Expand Up @@ -56,13 +58,33 @@ - (void)loadView {
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(launchButtonTapped)],
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonTapped)]
];

UIButton* openLinkButton = [UIButton buttonWithType:UIButtonTypeCustom];
openLinkButton.enabled = !!LCUtils.certificatePassword;
openLinkButton.frame = CGRectMake(0, 0, 40, 40);
[openLinkButton setImage:[UIImage systemImageNamed:@"link"] forState:UIControlStateNormal];
[openLinkButton addTarget:self action:@selector(openUrlButtonTapped) forControlEvents:UIControlEventTouchUpInside];

self.navigationItem.leftBarButtonItems = @[
[[UIBarButtonItem alloc] initWithCustomView:openLinkButton]
];

self.progressView = [[MBRoundProgressView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
if(self.pageUrlToOpen) {
[self openWebViewByURLString:self.pageUrlToOpen];
self.pageUrlToOpen = nil;
}
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];

NSString* webpageToOpen = [NSUserDefaults.standardUserDefaults objectForKey:@"webPageToOpen"];
if(webpageToOpen) {
[NSUserDefaults.standardUserDefaults removeObjectForKey:@"webPageToOpen"];
[self openWebViewByURLString:webpageToOpen];
}
}

- (void)addButtonTapped {
Expand Down Expand Up @@ -302,6 +324,20 @@ - (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void(^
} else {
[self.objects removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self showConfirmationDialogTitle:@"Delete Data Folder"
message:[NSString stringWithFormat:@"Do you also want to delete data folder of %@? You can keep it for future use.", appInfo.displayName]
destructive:YES
confirmButtonTitle:@"Delete"
handler:^(UIAlertAction * action) {
if (action.style != UIAlertActionStyleCancel) {
NSError *error = nil;
NSString* dataFolderPath = [NSString stringWithFormat:@"%@/Data/Application/%@", self.docPath, [appInfo dataUUID]];
[NSFileManager.defaultManager removeItemAtPath:dataFolderPath error:&error];
if (error) {
[self showDialogTitle:@"Error" message:error.localizedDescription];
}
}
}];
}
}
handler(YES);
Expand Down Expand Up @@ -615,4 +651,84 @@ - (UIMenu *)destructiveActionWithTitle:(NSString *)title image:(UIImage *)image
return menu;
}


- (void) openUrlButtonTapped {
[self showInputDialogTitle:@"Input URL" message:@"Input URL scheme or URL to a web page" placeholder:@"scheme://" callback:^(NSString *ans) {
dispatch_async(dispatch_get_main_queue(), ^(void){
[self openWebViewByURLString:ans];
});
return (NSString*)nil;
}];

}

- (void) openWebViewByURLString:(NSString*) urlString {
// wait for the loadView to call again.
if(!self.isViewLoaded) {
self.pageUrlToOpen = urlString;
return;
}

NSURLComponents* url = [[NSURLComponents alloc] initWithString:urlString];
if(!url) {
[self showDialogTitle:@"Invalid URL" message:@"The given URL is invalid. Check it and try again."];
} else {
NSMutableArray<LCAppInfo*>* apps = [[NSMutableArray alloc] init];
for(int i = 0; i < [self.objects count]; ++i) {
LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath: [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[i]]];
appInfo.relativeBundlePath = self.objects[i];
[apps insertObject:appInfo atIndex:i];
}

// use https for http and empty scheme
if([url.scheme length ] == 0) {
url.scheme = @"https";
} else if (![url.scheme isEqualToString: @"https"] && ![url.scheme isEqualToString: @"http"]){
[self launchAppByScheme:url apps:apps];
}
LCWebView *webViewController = [[LCWebView alloc] initWithURL:url.URL apps:apps];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:webViewController];
navController.navigationBar.translucent = NO;

navController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:navController animated:YES completion:nil];
}
}

- (void) launchAppByScheme:(NSURLComponents*)schemeURL apps:(NSMutableArray<LCAppInfo*>*)apps {
// find app
NSString* appId = nil;
for(int i = 0; i < [apps count] && !appId; ++i) {
LCAppInfo* nowApp = apps[i];
NSMutableArray* schemes = [nowApp urlSchemes];
if(!schemes) continue;
for(int j = 0; j < [schemes count]; ++j) {
NSString* nowScheme = schemes[j];
if([nowScheme isEqualToString:schemeURL.scheme]) {
appId = [nowApp relativeBundlePath];
break;
}
}
}
if (!appId) {
[self showDialogTitle:@"Invalid Scheme" message:@"LiveContainer cannot find an app that supports this scheme."];
return;
}


NSString* message = [NSString stringWithFormat:@"You are about to launch %@, continue?", appId];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[NSUserDefaults.standardUserDefaults setObject:appId forKey:@"selected"];
[NSUserDefaults.standardUserDefaults setObject:schemeURL.string forKey:@"launchAppUrlScheme"];
if ([LCUtils launchToGuestApp]) return;
}];
[alert addAction:okAction];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {

}];
[alert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:nil];
}

@end
3 changes: 3 additions & 0 deletions LiveContainerUI/LCTabBarController.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#import <UIKit/UIKit.h>
#import "LCAppListViewController.h"

@interface LCTabBarController : UITabBarController
@property() LCAppListViewController* appTableVC;
- (void) openWebPage:(NSString*) urlString;
@end
6 changes: 5 additions & 1 deletion LiveContainerUI/LCTabBarController.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#import "LCAppListViewController.h"
#import "LCSettingsListController.h"
#import "LCTweakListViewController.h"
#import "LCTabBarController.h"
Expand All @@ -10,6 +9,7 @@ - (void)loadView {

LCAppListViewController* appTableVC = [LCAppListViewController new];
appTableVC.title = @"Apps";
self.appTableVC = appTableVC;

LCTweakListViewController* tweakTableVC = [LCTweakListViewController new];
tweakTableVC.title = @"Tweaks";
Expand All @@ -28,4 +28,8 @@ - (void)loadView {
self.viewControllers = @[appNavigationController, tweakNavigationController, settingsNavigationController];
}

- (void) openWebPage:(NSString*) urlString {
[self.appTableVC openWebViewByURLString:urlString];
}

@end
11 changes: 11 additions & 0 deletions LiveContainerUI/LCWebView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import "LCAppInfo.h"

@interface LCWebView : UIViewController <WKNavigationDelegate>
- (instancetype)initWithURL:(NSURL *)url apps:(NSMutableArray<LCAppInfo*>*)apps;
- (void)askIfLaunchApp:(NSString*)appId url:(NSURL*)launchUrl;
@property (nonatomic) NSURL *url;
@property (nonatomic) NSMutableArray<LCAppInfo*>* apps;
@property (strong, nonatomic) WKWebView *webView;
@end
Loading

0 comments on commit 10a49ba

Please sign in to comment.