#import "GBHubGameViewController.h" #import "GBROMManager.h" #import "UILabel+TapLocation.h" @implementation NSMutableAttributedString (append) - (void)appendWithAttributes:(NSDictionary *)attributes format:(NSString *)format, ... { va_list args; va_start(args, format); NSString *string = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); [self appendAttributedString:[[NSAttributedString alloc] initWithString:string attributes:attributes]]; } @end @implementation GBHubGameViewController { GBHubGame *_game; UIScrollView *_scrollView; UIScrollView *_screenshotsScrollView; UILabel *_titleLabel; UILabel *_descriptionLabel; } - (instancetype)initWithGame:(GBHubGame *)game { self = [super init]; _game = game; return self; } - (void)viewDidLoad { [super viewDidLoad]; UIColor *labelColor, *linkColor, *secondaryLabelColor; if (@available(iOS 13.0, *)) { self.view.backgroundColor = [UIColor systemBackgroundColor]; labelColor = UIColor.labelColor; linkColor = UIColor.linkColor; secondaryLabelColor = UIColor.secondaryLabelColor; } else { self.view.backgroundColor = [UIColor whiteColor]; labelColor = UIColor.blackColor; linkColor = UIColor.blueColor; secondaryLabelColor = [UIColor colorWithWhite:0.55 alpha:1.0]; } _scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _scrollView.scrollEnabled = true; _scrollView.pagingEnabled = false; _scrollView.showsVerticalScrollIndicator = true; _scrollView.showsHorizontalScrollIndicator = false; [self.view addSubview:_scrollView]; _scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height * 2); _titleLabel = [[UILabel alloc] initWithFrame:(CGRectMake(0, 8, self.view.bounds.size.width - 16, 56))]; NSMutableParagraphStyle *style = [NSParagraphStyle defaultParagraphStyle].mutableCopy; style.paragraphSpacing = 4; NSMutableAttributedString *titleText = [[NSMutableAttributedString alloc] init]; [titleText appendWithAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:34 weight:UIFontWeightBold], NSForegroundColorAttributeName: labelColor, NSParagraphStyleAttributeName: style, } format:@"%@", _game.title]; [titleText appendWithAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:20], NSForegroundColorAttributeName: secondaryLabelColor, NSParagraphStyleAttributeName: style, } format:@"\n by %@", _game.developer]; _titleLabel.attributedText = titleText; _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; _titleLabel.numberOfLines = 0; [_scrollView addSubview:_titleLabel]; NSMutableAttributedString *text = [[NSMutableAttributedString alloc] init]; NSDictionary *labelAttributes = @{ NSFontAttributeName: [UIFont boldSystemFontOfSize:UIFont.labelFontSize], NSForegroundColorAttributeName: labelColor, }; NSDictionary *valueAttributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:UIFont.labelFontSize], NSForegroundColorAttributeName: labelColor, }; if (_game.entryDescription) { [text appendWithAttributes:valueAttributes format:@"%@\n\n", _game.entryDescription]; } if (_game.publicationDate) { [text appendWithAttributes:labelAttributes format:@"Published: "]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateStyle = NSDateFormatterMediumStyle; formatter.timeStyle = NSDateFormatterNoStyle; formatter.locale = [NSLocale currentLocale]; [text appendWithAttributes:valueAttributes format:@"%@\n", [formatter stringFromDate:_game.publicationDate]]; } if (_game.website) { [text appendWithAttributes:labelAttributes format:@"Website: "]; [text appendWithAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:UIFont.labelFontSize], @"GBLinkAttribute": _game.website, NSForegroundColorAttributeName: linkColor, } format:@"%@\n", _game.website]; } if (_game.repository) { [text appendWithAttributes:labelAttributes format:@"Repository: "]; [text appendWithAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:UIFont.labelFontSize], @"GBLinkAttribute": _game.repository, NSForegroundColorAttributeName: linkColor, } format:@"%@\n", _game.repository]; } if (_game.license) { [text appendWithAttributes:labelAttributes format:@"License: "]; [text appendWithAttributes:valueAttributes format:@"%@\n", _game.license]; } if (_game.tags.count) { [text appendWithAttributes:labelAttributes format:@"Categories: "]; bool first = true; for (NSString *tag in _game.tags) { if (!first) { [text appendWithAttributes:valueAttributes format:@", ", _game.license]; } first = false; [text appendWithAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:UIFont.labelFontSize], @"GBHubTag": tag, NSForegroundColorAttributeName: linkColor, } format:@"%@", tag]; } [text appendWithAttributes:valueAttributes format:@"\n"]; } _descriptionLabel = [[UILabel alloc] init]; _descriptionLabel.numberOfLines = 0; _descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping; if (@available(iOS 14.0, *)) { _descriptionLabel.lineBreakStrategy = NSLineBreakStrategyNone; } _descriptionLabel.attributedText = text; [_scrollView addSubview:_descriptionLabel]; unsigned screenshotWidth = (unsigned)(MIN(self.view.bounds.size.width, self.view.bounds.size.height) - 16) / 160 * 160; unsigned screenshotHeight = screenshotWidth / 160 * 144; if (_game.screenshots.count) { _screenshotsScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, screenshotHeight + 8)]; _screenshotsScrollView.scrollEnabled = true; _screenshotsScrollView.pagingEnabled = false; _screenshotsScrollView.showsVerticalScrollIndicator = false; _screenshotsScrollView.showsHorizontalScrollIndicator = true; unsigned x = 0; for (NSURL *url in _game.screenshots) { x += 8; UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, 0, screenshotWidth, screenshotHeight)]; [imageView.layer setMinificationFilter:kCAFilterLinear]; [imageView.layer setMagnificationFilter:kCAFilterNearest]; imageView.layer.cornerRadius = 4; imageView.layer.borderWidth = 1; imageView.layer.masksToBounds = true; if (@available(iOS 13.0, *)) { imageView.layer.borderColor = [UIColor tertiaryLabelColor].CGColor; } else { imageView.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.5].CGColor; } [_screenshotsScrollView addSubview:imageView]; [[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if (location) { UIImage *image = [UIImage imageWithContentsOfFile:location.path]; dispatch_async(dispatch_get_main_queue(), ^{ CGRect frame = imageView.frame; imageView.image = image; imageView.frame = frame; imageView.contentMode = UIViewContentModeScaleAspectFit; }); } }] resume]; x += screenshotWidth + 8; } _screenshotsScrollView.contentSize = CGSizeMake(x, screenshotHeight); _screenshotsScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; [_scrollView addSubview:_screenshotsScrollView]; } [self viewDidLayoutSubviews]; _descriptionLabel.userInteractionEnabled = true; [_descriptionLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedLabel:)]]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Download" style:UIBarButtonItemStylePlain target:self action:@selector(rightButtonPressed)]; if ([GBROMManager.sharedManager romFileForROM:_game.title]) { self.navigationItem.rightBarButtonItem.title = @"Open"; } } - (void)rightButtonPressed { if ([GBROMManager.sharedManager romFileForROM:_game.title]) { [GBROMManager sharedManager].currentROM = _game.title; [self.navigationController dismissViewControllerAnimated:true completion:nil]; } else { UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleWhite; if (@available(iOS 13.0, *)) { style = UIActivityIndicatorViewStyleMedium; } UIActivityIndicatorView *view = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:style]; CGRect frame = view.frame; frame.size.width += 16; view.frame = frame; [view startAnimating]; self.navigationItem.rightBarButtonItem.customView = view; [[[NSURLSession sharedSession] downloadTaskWithURL:_game.file completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if (!location) { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Could not download ROM" message:@"Could not download this ROM from Homebrew Hub. Please try again later." preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alert animated:true completion:nil]; self.navigationItem.rightBarButtonItem.customView = nil; }); return; } NSString *newTempName = [[location.path stringByDeletingLastPathComponent] stringByAppendingPathComponent:_game.file.lastPathComponent]; [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:newTempName error:nil]; [[GBROMManager sharedManager] importROM:newTempName withName:_game.title keepOriginal:false]; dispatch_async(dispatch_get_main_queue(), ^{ self.navigationItem.rightBarButtonItem.title = @"Open"; self.navigationItem.rightBarButtonItem.customView = nil; }); }] resume]; } } - (void)tappedLabel:(UITapGestureRecognizer *)tap { unsigned characterIndex = [(UILabel *)tap.view characterAtTap:tap]; NSURL *url = [((UILabel *)tap.view).attributedText attribute:@"GBLinkAttribute" atIndex:characterIndex effectiveRange:NULL]; if (url) { [[UIApplication sharedApplication] openURL:url options:nil completionHandler:nil]; return; } NSString *tag = [((UILabel *)tap.view).attributedText attribute:@"GBHubTag" atIndex:characterIndex effectiveRange:NULL]; if (tag) { UINavigationItem *parent = self.navigationController.navigationBar.items[self.navigationController.navigationBar.items.count - 2]; [self.navigationController popViewControllerAnimated:true]; if (@available(iOS 13.0, *)) { parent.searchController.searchBar.searchTextField.text = @""; parent.searchController.searchBar.searchTextField.tokens = nil; UISearchToken *token = [UISearchToken tokenWithIcon:nil text:tag]; token.representedObject = tag; [parent.searchController.searchBar.searchTextField insertToken:token atIndex:0]; } else { parent.searchController.searchBar.text = tag; } parent.searchController.active = true; return; } } - (void)viewDidLayoutSubviews { unsigned y = 12; CGSize size = [_titleLabel sizeThatFits:(CGSize){_scrollView.bounds.size.width - 32, INFINITY}];; _titleLabel.frame = (CGRect){{16, y}, {_scrollView.bounds.size.width - 32, size.height}}; y += size.height + 24; if (_screenshotsScrollView) { _screenshotsScrollView.frame = CGRectMake(0, y, _scrollView.bounds.size.width, _screenshotsScrollView.frame.size.height); y += _screenshotsScrollView.frame.size.height + 8; if (_game.screenshots.count == 1) { CGRect frame = _screenshotsScrollView.frame; frame.origin.x = (_scrollView.bounds.size.width - _screenshotsScrollView.contentSize.width) / 2; _screenshotsScrollView.frame = frame; } } size = [_descriptionLabel sizeThatFits:(CGSize){_scrollView.bounds.size.width - 32, INFINITY}];; _descriptionLabel.frame = (CGRect){{16, y}, {_scrollView.bounds.size.width - 32, size.height}}; y += size.height; _scrollView.contentSize = CGSizeMake(_scrollView.bounds.size.width, y); } @end