Files
PojavLauncher_iOS/Natives/installer/ForgeInstallViewController.m
khanhduytran0 4c075f6941 [WIP] Add modpack installation
Added Modrinth, Fabric/Quilt automated installation
2024-02-14 10:16:13 +07:00

222 lines
9.4 KiB
Objective-C

#import "AFNetworking.h"
#import "ForgeInstallViewController.h"
#import "LauncherNavigationController.h"
#import "WFWorkflowProgressView.h"
#import "ios_uikit_bridge.h"
#import "utils.h"
#include <dlfcn.h>
@interface ForgeInstallViewController()<NSXMLParserDelegate>
@property(atomic) AFURLSessionManager *afManager;
@property(nonatomic) WFWorkflowProgressView *progressView;
@property(nonatomic) NSDictionary *endpoints;
@property(nonatomic) NSMutableArray<NSNumber *> *visibilityList;
@property(nonatomic) NSMutableArray<NSString *> *versionList;
@property(nonatomic) NSMutableArray<NSMutableArray *> *forgeList;
@property(nonatomic, assign) BOOL isVersionElement;
@end
@implementation ForgeInstallViewController
- (void)viewDidLoad {
[super viewDidLoad];
UISegmentedControl *segment = [[UISegmentedControl alloc] initWithItems:@[@"Forge", @"NeoForge"]];
segment.selectedSegmentIndex = 0;
[segment addTarget:self action:@selector(segmentChanged:) forControlEvents:UIControlEventValueChanged];
self.navigationItem.titleView = segment;
// Load WFWorkflowProgressView
dlopen("/System/Library/PrivateFrameworks/WorkflowUIServices.framework/WorkflowUIServices", RTLD_GLOBAL);
self.progressView = [[NSClassFromString(@"WFWorkflowProgressView") alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
self.progressView.resolvedTintColor = self.view.tintColor;
[self.progressView addTarget:self
action:@selector(actionCancelDownload) forControlEvents:UIControlEventTouchUpInside];
self.endpoints = @{
@"Forge": @{
@"installer": @"https://maven.minecraftforge.net/net/minecraftforge/forge/%1$@/forge-%[email protected]",
@"metadata": @"https://maven.minecraftforge.net/net/minecraftforge/forge/maven-metadata.xml"
},
@"NeoForge": @{
@"installer": @"https://maven.neoforged.net/net/neoforged/forge/%1$@/forge-%[email protected]",
@"metadata": @"https://maven.neoforged.net/releases/net/neoforged/forge/maven-metadata.xml"
}
};
self.visibilityList = [NSMutableArray new];
self.versionList = [NSMutableArray new];
self.forgeList = [NSMutableArray new];
[self loadMetadataFromVendor:@"Forge"];
}
- (void)actionCancelDownload {
[self.afManager invalidateSessionCancelingTasks:YES resetSession:NO];
}
- (void)actionClose {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
- (void)loadMetadataFromVendor:(NSString *)vendor {
[self switchToLoadingState];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [[NSURL alloc] initWithString:self.endpoints[vendor][@"metadata"]];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
parser.delegate = self;
if (![parser parse]) {
dispatch_async(dispatch_get_main_queue(), ^{
showDialog(localize(@"Error", nil), parser.parserError.localizedDescription);
[self actionClose];
});
}
});
}
- (void)switchToLoadingState {
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:indicator];
[indicator startAnimating];
self.navigationController.modalInPresentation = YES;
}
- (void)switchToReadyState {
UIActivityIndicatorView *indicator = (id)self.navigationItem.rightBarButtonItem.customView;
[indicator stopAnimating];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemClose target:self action:@selector(actionClose)];
self.navigationController.modalInPresentation = NO;
}
- (void)segmentChanged:(UISegmentedControl *)segment {
[self.visibilityList removeAllObjects];
[self.versionList removeAllObjects];
[self.forgeList removeAllObjects];
[self.tableView reloadData];
NSString *vendor = [segment titleForSegmentAtIndex:segment.selectedSegmentIndex];
[self loadMetadataFromVendor:vendor];
}
#pragma mark UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.versionList.count;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UITableViewHeaderFooterView *view = [self.tableView dequeueReusableHeaderFooterViewWithIdentifier:@"section"];
if (!view) {
view = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:@"section"];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tableViewDidSelectSection:)];
[view addGestureRecognizer:tapGesture];
}
return view;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return self.versionList[section];
}
- (void)tableViewDidSelectSection:(UITapGestureRecognizer *)sender {
UITableViewHeaderFooterView *view = (id)sender.view;
int section = [self.versionList indexOfObject:view.textLabel.text];
self.visibilityList[section] = @(!self.visibilityList[section].boolValue);
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.visibilityList[section].boolValue ? self.forgeList[section].count : 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
cell.textLabel.text = self.forgeList[indexPath.section][indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
tableView.allowsSelection = NO;
[self switchToLoadingState];
self.progressView.fractionCompleted = 0;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryView = self.progressView;
UISegmentedControl *segment = (id)self.navigationItem.titleView;
NSString *vendor = [segment titleForSegmentAtIndex:segment.selectedSegmentIndex];
NSString *jarURL = [NSString stringWithFormat:self.endpoints[vendor][@"installer"], cell.textLabel.text];
NSString *outPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.jar"];
NSDebugLog(@"[Forge Installer] Downloading %@", jarURL);
self.afManager = [AFURLSessionManager new];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:jarURL]];
NSURLSessionDownloadTask *downloadTask = [self.afManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull progress){
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.fractionCompleted = progress.fractionCompleted;
});
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
[NSFileManager.defaultManager removeItemAtPath:outPath error:nil];
return [NSURL fileURLWithPath:outPath];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
tableView.allowsSelection = YES;
cell.accessoryView = nil;
if (error) {
if (error.code != NSURLErrorCancelled) {
NSDebugLog(@"Error: %@", error);
showDialog(localize(@"Error", nil), error.localizedDescription);
}
[self switchToReadyState];
return;
}
LauncherNavigationController *navVC = (id)((UISplitViewController *)self.presentingViewController).viewControllers[1];
[self dismissViewControllerAnimated:YES completion:^{
[navVC enterModInstallerWithPath:outPath hitEnterAfterWindowShown:YES];
}];
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[downloadTask resume];
});
}
- (void)addVersionToList:(NSString *)version {
if (![version containsString:@"-"]) {
return;
}
NSRange range = [version rangeOfString:@"-"];
NSString *gameVersion = [version substringToIndex:range.location];
//NSString *forgeVersion = [version substringFromIndex:range.location + 1];
if (![self.versionList containsObject:gameVersion]) {
[self.visibilityList addObject:@(NO)];
[self.versionList addObject:gameVersion];
[self.forgeList addObject:[NSMutableArray new]];
}
[self.forgeList.lastObject addObject:version];
}
#pragma mark NSXMLParser
- (void)parserDidEndDocument:(NSXMLParser *)unused {
dispatch_async(dispatch_get_main_queue(), ^{
[self switchToReadyState];
[self.tableView reloadData];
});
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
self.isVersionElement = [elementName isEqualToString:@"version"];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)version {
if (self.isVersionElement) {
[self addVersionToList:version];
}
}
@end