#import "AFNetworking.h" #import "LauncherNavigationController.h" #import "ModpackInstallViewController.h" #import "UIKit+AFNetworking.h" #import "UIKit+hook.h" #import "WFWorkflowProgressView.h" #import "modpack/ModrinthAPI.h" #import "config.h" #import "ios_uikit_bridge.h" #import "utils.h" #include #define kCurseForgeGameIDMinecraft 432 #define kCurseForgeClassIDModpack 4471 #define kCurseForgeClassIDMod 6 @interface ModpackInstallViewController() @property(nonatomic) UISearchController *searchController; @property(nonatomic) UIMenu *currentMenu; @property(nonatomic) NSMutableArray *list; @property(nonatomic) NSMutableDictionary *filters; @property ModrinthAPI *modrinth; @end @implementation ModpackInstallViewController - (void)viewDidLoad { [super viewDidLoad]; //NSString *curseforgeAPIKey = CONFIG_CURSEFORGE_API_KEY; self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil]; self.searchController.searchResultsUpdater = self; self.searchController.obscuresBackgroundDuringPresentation = NO; self.navigationItem.searchController = self.searchController; self.modrinth = [ModrinthAPI new]; self.filters = @{ @"isModpack": @(YES), @"name": @" " // mcVersion }.mutableCopy; [self updateSearchResults]; } - (void)loadSearchResultsWithPrevList:(BOOL)prevList { NSString *name = self.searchController.searchBar.text; if (!prevList && [self.filters[@"name"] isEqualToString:name]) { return; } [self switchToLoadingState]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.filters[@"name"] = name; self.list = [self.modrinth searchModWithFilters:self.filters previousPageResult:prevList ? self.list : nil]; dispatch_async(dispatch_get_main_queue(), ^{ if (self.list) { [self switchToReadyState]; [self.tableView reloadData]; } else { showDialog(localize(@"Error", nil), self.modrinth.lastError.localizedDescription); [self actionClose]; } }); }); } - (void)updateSearchResults { [self loadSearchResultsWithPrevList:NO]; } - (void)updateSearchResultsForSearchController:(UISearchController *)searchController { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(updateSearchResults) object:nil]; [self performSelector:@selector(updateSearchResults) withObject:nil afterDelay:0.5]; } - (void)actionClose { [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } - (void)switchToLoadingState { UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:indicator]; [indicator startAnimating]; self.navigationController.modalInPresentation = YES; self.tableView.allowsSelection = NO; } - (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; self.tableView.allowsSelection = YES; } #pragma mark UIContextMenu - (UIContextMenuConfiguration *)contextMenuInteraction:(UIContextMenuInteraction *)interaction configurationForMenuAtLocation:(CGPoint)location { return [UIContextMenuConfiguration configurationWithIdentifier:nil previewProvider:nil actionProvider:^UIMenu * _Nullable(NSArray * _Nonnull suggestedActions) { return self.currentMenu; }]; } - (_UIContextMenuStyle *)_contextMenuInteraction:(UIContextMenuInteraction *)interaction styleForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration { _UIContextMenuStyle *style = [_UIContextMenuStyle defaultStyle]; style.preferredLayout = 3; // _UIContextMenuLayoutCompactMenu return style; } #pragma mark UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.list.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; cell.imageView.contentMode = UIViewContentModeScaleToFill; cell.imageView.clipsToBounds = YES; } NSDictionary *item = self.list[indexPath.row]; cell.textLabel.text = item[@"title"]; cell.detailTextLabel.text = item[@"description"]; UIImage *fallbackImage = [UIImage imageNamed:@"DefaultProfile"]; [cell.imageView setImageWithURL:[NSURL URLWithString:item[@"imageUrl"]] placeholderImage:fallbackImage]; if (!self.modrinth.reachedLastPage && indexPath.row == self.list.count-1) { [self loadSearchResultsWithPrevList:YES]; } return cell; } - (void)showDetails:(NSDictionary *)details atIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; NSMutableArray *menuItems = [[NSMutableArray alloc] init]; [details[@"versionNames"] enumerateObjectsUsingBlock: ^(NSString *name, NSUInteger i, BOOL *stop) { NSString *nameWithVersion = name; NSString *mcVersion = details[@"mcVersionNames"][i]; if (![name hasSuffix:mcVersion]) { nameWithVersion = [NSString stringWithFormat:@"%@ - %@", name, mcVersion]; } [menuItems addObject:[UIAction actionWithTitle:nameWithVersion image:nil identifier:nil handler:^(UIAction *action) { [self actionClose]; NSString *tmpIconPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"icon.png"]; [UIImagePNGRepresentation([cell.imageView.image _imageWithSize:CGSizeMake(40, 40)]) writeToFile:tmpIconPath atomically:YES]; [self.modrinth installModpackFromDetail:self.list[indexPath.row] atIndex:i]; }]]; }]; self.currentMenu = [UIMenu menuWithTitle:@"" children:menuItems]; UIContextMenuInteraction *interaction = [[UIContextMenuInteraction alloc] initWithDelegate:self]; cell.detailTextLabel.interactions = @[interaction]; [interaction _presentMenuAtLocation:CGPointZero]; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSDictionary *item = self.list[indexPath.row]; if ([item[@"versionDetailsLoaded"] boolValue]) { [self showDetails:item atIndexPath:indexPath]; return; } [tableView deselectRowAtIndexPath:indexPath animated:NO]; [self switchToLoadingState]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.modrinth loadDetailsOfMod:self.list[indexPath.row]]; dispatch_async(dispatch_get_main_queue(), ^{ [self switchToReadyState]; if ([item[@"versionDetailsLoaded"] boolValue]) { [self showDetails:item atIndexPath:indexPath]; } else { showDialog(localize(@"Error", nil), self.modrinth.lastError.localizedDescription); } }); }); } @end