Files
PojavLauncher_iOS/Natives/LauncherNavigationController.m
T

367 lines
16 KiB
Objective-C

#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "authenticator/BaseAuthenticator.h"
#import "AFNetworking.h"
#import "CustomControlsViewController.h"
#import "JavaGUIViewController.h"
#import "LauncherMenuViewController.h"
#import "LauncherNavigationController.h"
#import "LauncherPreferences.h"
#import "MinecraftResourceUtils.h"
#import "PickTextField.h"
#import "PLPickerView.h"
#import "PLProfiles.h"
#import "ios_uikit_bridge.h"
#import "UIKit+hook.h"
#import "utils.h"
#define AUTORESIZE_MASKS UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin
@interface LauncherNavigationController () <UIDocumentPickerDelegate, UIPickerViewDataSource, PLPickerViewDelegate, UIPopoverPresentationControllerDelegate> {
}
@property(nonatomic) PLPickerView* versionPickerView;
@property(nonatomic) UITextField* versionTextField;
@property(nonatomic) int profileSelectedAt;
@end
@implementation LauncherNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
if ([self respondsToSelector:@selector(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
self.versionTextField = [[PickTextField alloc] initWithFrame:CGRectMake(4, 4, self.toolbar.frame.size.width * 0.8 - 8, self.toolbar.frame.size.height - 8)];
[self.versionTextField addTarget:self.versionTextField action:@selector(resignFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit];
self.versionTextField.autoresizingMask = AUTORESIZE_MASKS;
self.versionTextField.placeholder = @"Specify version...";
self.versionTextField.leftView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
self.versionTextField.rightView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"SpinnerArrow"] _imageWithSize:CGSizeMake(30, 30)]];
self.versionTextField.rightView.frame = CGRectMake(0, 0, self.versionTextField.frame.size.height * 0.9, self.versionTextField.frame.size.height * 0.9);
self.versionTextField.leftViewMode = UITextFieldViewModeAlways;
self.versionTextField.rightViewMode = UITextFieldViewModeAlways;
self.versionTextField.textAlignment = NSTextAlignmentCenter;
self.versionPickerView = [[PLPickerView alloc] init];
self.versionPickerView.delegate = self;
self.versionPickerView.dataSource = self;
UIToolbar *versionPickToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, 44.0)];
[self reloadProfileList];
UIBarButtonItem *versionFlexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
UIBarButtonItem *versionDoneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(versionClosePicker)];
versionPickToolbar.items = @[versionFlexibleSpace, versionDoneButton];
self.versionTextField.inputAccessoryView = versionPickToolbar;
self.versionTextField.inputView = self.versionPickerView;
UIView *targetToolbar;
targetToolbar = self.toolbar;
[targetToolbar addSubview:self.versionTextField];
self.progressViewMain = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, self.toolbar.frame.size.width, 4)];
self.progressViewSub = [[UIProgressView alloc] initWithFrame:CGRectMake(0, self.toolbar.frame.size.height - 4, self.toolbar.frame.size.width, 4)];
self.progressViewMain.autoresizingMask = self.progressViewSub.autoresizingMask = AUTORESIZE_MASKS;
self.progressViewMain.hidden = self.progressViewSub.hidden = YES;
[targetToolbar addSubview:self.progressViewMain];
[targetToolbar addSubview:self.progressViewSub];
self.buttonInstall = [UIButton buttonWithType:UIButtonTypeSystem];
setButtonPointerInteraction(self.buttonInstall);
[self.buttonInstall setTitle:localize(@"Play", nil) forState:UIControlStateNormal];
self.buttonInstall.autoresizingMask = AUTORESIZE_MASKS;
self.buttonInstall.backgroundColor = [UIColor colorWithRed:54/255.0 green:176/255.0 blue:48/255.0 alpha:1.0];
self.buttonInstall.layer.cornerRadius = 5;
self.buttonInstall.frame = CGRectMake(self.toolbar.frame.size.width * 0.8, 4, self.toolbar.frame.size.width * 0.2, self.toolbar.frame.size.height - 8);
self.buttonInstall.tintColor = UIColor.whiteColor;
[self.buttonInstall addTarget:self action:@selector(launchMinecraft:) forControlEvents:UIControlEventPrimaryActionTriggered];
[targetToolbar addSubview:self.buttonInstall];
self.progressText = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.toolbar.frame.size.width, self.toolbar.frame.size.height)];
self.progressText.adjustsFontSizeToFitWidth = YES;
self.progressText.autoresizingMask = AUTORESIZE_MASKS;
self.progressText.font = [self.progressText.font fontWithSize:16];
self.progressText.textAlignment = NSTextAlignmentCenter;
self.progressText.userInteractionEnabled = NO;
[targetToolbar addSubview:self.progressText];
self.buttonInstall.enabled = NO;
[self fetchRemoteVersionList];
if ([BaseAuthenticator.current isKindOfClass:MicrosoftAuthenticator.class]) {
// Perform token refreshment on startup
[self setInteractionEnabled:NO];
id callback = ^(NSString* status, BOOL success) {
self.progressText.text = status;
if (status == nil) {
[self setInteractionEnabled:YES];
} else if (!success) {
showDialog(localize(@"Error", nil), status);
}
};
[BaseAuthenticator.current refreshTokenWithCallback:callback];
}
}
- (BOOL)isVersionInstalled:(NSString *)versionId {
NSString *localPath = [NSString stringWithFormat:@"%s/versions/%@", getenv("POJAV_GAME_DIR"), versionId];
BOOL isDirectory;
[NSFileManager.defaultManager fileExistsAtPath:localPath isDirectory:&isDirectory];
return isDirectory;
}
- (void)fetchLocalVersionList {
if (!localVersionList) {
localVersionList = [NSMutableArray new];
}
[localVersionList removeAllObjects];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *versionPath = [NSString stringWithFormat:@"%s/versions/", getenv("POJAV_GAME_DIR")];
NSArray *list = [fileManager contentsOfDirectoryAtPath:versionPath error:Nil];
for (NSString *versionId in list) {
if (![self isVersionInstalled:versionId]) continue;
[localVersionList addObject:@{
@"id": versionId,
@"type": @"custom"
}];
}
}
- (void)fetchRemoteVersionList {
self.buttonInstall.enabled = NO;
remoteVersionList = @[
@{@"id": @"latest-release", @"type": @"release"},
@{@"id": @"latest-snapshot", @"type": @"snapshot"}
].mutableCopy;
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" parameters:nil headers:nil progress:^(NSProgress * _Nonnull progress) {
self.progressViewMain.progress = progress.fractionCompleted;
} success:^(NSURLSessionTask *task, NSDictionary *responseObject) {
[remoteVersionList addObjectsFromArray:responseObject[@"versions"]];
NSDebugLog(@"[VersionList] Got %d versions", remoteVersionList.count);
setPrefObject(@"internal.latest_version", responseObject[@"latest"]);
self.buttonInstall.enabled = YES;
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSDebugLog(@"[VersionList] Warning: Unable to fetch version list: %@", error.localizedDescription);
self.buttonInstall.enabled = YES;
}];
}
// Invoked by: startup, instance change event
- (void)reloadProfileList {
// Reload local version list
[self fetchLocalVersionList];
// Reload launcher_profiles.json
[PLProfiles updateCurrent];
[self.versionPickerView reloadAllComponents];
// Reload selected profile info
self.profileSelectedAt = [PLProfiles.current.profiles.allKeys indexOfObject:PLProfiles.current.selectedProfileName];
if (self.profileSelectedAt == -1) {
// This instance has no profiles?
return;
}
[self.versionPickerView selectRow:self.profileSelectedAt inComponent:0 animated:NO];
[self pickerView:self.versionPickerView didSelectRow:self.profileSelectedAt inComponent:0];
}
#pragma mark - Options
- (void)enterCustomControls {
CustomControlsViewController *vc = [[CustomControlsViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
vc.setDefaultCtrl = ^(NSString *name){
setPrefObject(@"control.default_ctrl", name);
};
vc.getDefaultCtrl = ^{
return getPrefObject(@"control.default_ctrl");
};
[self presentViewController:vc animated:YES completion:nil];
}
- (void)enterModInstaller {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc]
initForOpeningContentTypes:@[[UTType typeWithMIMEType:@"application/java-archive"]]
asCopy:YES];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url {
JavaGUIViewController *vc = [[JavaGUIViewController alloc] init];
vc.filepath = url.path;
if (!vc.requiredJavaVersion) {
return;
}
[self invokeAfterJITEnabled:^{
vc.modalPresentationStyle = UIModalPresentationFullScreen;
NSLog(@"[ModInstaller] launching %@", vc.filepath);
[self presentViewController:vc animated:YES completion:nil];
}];
}
- (void)setInteractionEnabled:(BOOL)enabled {
for (UIControl *view in self.toolbar.subviews) {
if ([view isKindOfClass:UIControl.class]) {
view.alpha = enabled ? 1 : 0.2;
view.enabled = enabled;
}
}
self.progressViewMain.hidden = self.progressViewSub.hidden = enabled;
}
- (void)launchMinecraft:(UIButton *)sender {
if (!self.versionTextField.hasText) {
[self.versionTextField becomeFirstResponder];
return;
}
if (BaseAuthenticator.current == nil) {
// Present the account selector if none selected
UIViewController *view = [(UINavigationController *)self.splitViewController.viewControllers[0]
viewControllers][0];
[view performSelector:@selector(selectAccount:) withObject:sender];
return;
}
sender.alpha = 0.5;
[self setInteractionEnabled:NO];
NSString *versionId = PLProfiles.current.profiles[self.versionTextField.text][@"lastVersionId"];
NSDictionary *object = [remoteVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", versionId]].firstObject;
if (!object) {
object = [localVersionList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"(id == %@)", versionId]].firstObject;
}
[MinecraftResourceUtils downloadVersion:object callback:^(NSString *stage, NSProgress *mainProgress, NSProgress *progress) {
if (progress == nil && stage != nil) {
NSLog(@"[MCDL] %@", stage);
}
self.progressViewMain.observedProgress = mainProgress;
self.progressViewSub.observedProgress = progress;
if (stage == nil) {
sender.alpha = 1;
self.progressText.text = nil;
[self setInteractionEnabled:YES];
if (mainProgress != nil) {
[self invokeAfterJITEnabled:^{
UIKit_launchMinecraftSurfaceVC();
}];
}
return;
}
NSString *completed = [NSByteCountFormatter stringFromByteCount:progress.completedUnitCount countStyle:NSByteCountFormatterCountStyleMemory];
NSString *total = [NSByteCountFormatter stringFromByteCount:progress.totalUnitCount countStyle:NSByteCountFormatterCountStyleMemory];
self.progressText.text = [NSString stringWithFormat:@"%@ (%@ / %@)", stage, completed, total];
}];
//callback_LauncherViewController_installMinecraft("1.12.2");
}
- (void)invokeAfterJITEnabled:(void(^)(void))handler {
localVersionList = remoteVersionList = nil;
if (isJITEnabled(false)) {
dispatch_async(dispatch_get_main_queue(), ^{
handler();
});
return;
} else if (getPrefBool(@"debug.debug_skip_wait_jit")) {
NSLog(@"Debug option skipped waiting for JIT. Java might not work.");
handler();
return;
}
//CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(tickJIT)];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:localize(@"launcher.wait_jit.title", nil)
message:localize(@"launcher.wait_jit.message", nil)
preferredStyle:UIAlertControllerStyleAlert];
/* TODO:
UIAlertAction *cancel = [UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^{
}];
[alert addAction:cancel];
*/
[self presentViewController:alert animated:YES completion:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!isJITEnabled(false)) {
// Perform check for every second
sleep(1);
}
dispatch_async(dispatch_get_main_queue(), ^{
[alert dismissViewControllerAnimated:YES completion:handler];
});
});
}
#pragma mark - UIPopoverPresentationControllerDelegate
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
return UIModalPresentationNone;
}
#pragma mark - UIPickerView stuff
- (void)pickerView:(PLPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
self.profileSelectedAt = row;
/*
if (self.versionList.count == 0) {
self.versionTextField.text = @"";
return;
}
*/
((UIImageView *)self.versionTextField.leftView).image = [pickerView imageAtRow:row column:component];
self.versionTextField.text = [self pickerView:pickerView titleForRow:row forComponent:component];
PLProfiles.current.selectedProfileName = self.versionTextField.text;
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return PLProfiles.current.profiles.count;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return PLProfiles.current.profiles.allValues[row][@"name"];
}
- (UIImage *)pickerView:(UIPickerView *)pickerView imageForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *urlString = PLProfiles.current.profiles.allValues[row][@"icon"];
if (!urlString) {
return [[UIImage imageNamed:@"DefaultProfile"] _imageWithSize:CGSizeMake(40, 40)];
}
NSString *iconStr = [urlString stringByReplacingOccurrencesOfString:@"data:image/png;base64," withString:@""];
NSData *iconData = [[NSData alloc] initWithBase64EncodedString:iconStr options:NSDataBase64DecodingIgnoreUnknownCharacters];
return [[UIImage imageWithData:iconData] _imageWithSize:CGSizeMake(40, 40)];
}
- (void)versionClosePicker {
[self.versionTextField endEditing:YES];
[self pickerView:self.versionPickerView didSelectRow:[self.versionPickerView selectedRowInComponent:0] inComponent:0];
}
#pragma mark - View controller UI mode
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeBottom;
}
- (BOOL)prefersHomeIndicatorAutoHidden {
return YES;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[sidebarViewController updateAccountInfo];
}
@end