From f55daf941c46dc406e903c0e5462843a796121a4 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 2 Nov 2020 20:32:57 -0600 Subject: [PATCH] Add support for hiding apps --- Limelight/Database/TemporaryApp.h | 1 + Limelight/Database/TemporaryApp.m | 2 + .../Moonlight v1.6.xcdatamodel/contents | 3 +- Limelight/UIAppView.h | 1 + Limelight/UIAppView.m | 16 ++- .../ViewControllers/MainFrameViewController.m | 124 +++++++++++++++--- 6 files changed, 128 insertions(+), 19 deletions(-) diff --git a/Limelight/Database/TemporaryApp.h b/Limelight/Database/TemporaryApp.h index 62d95a6..0a205fc 100644 --- a/Limelight/Database/TemporaryApp.h +++ b/Limelight/Database/TemporaryApp.h @@ -15,6 +15,7 @@ @property (nullable, nonatomic, retain) NSString *name; @property (nullable, nonatomic, retain) NSString *installPath; @property (nonatomic) BOOL hdrSupported; +@property (nonatomic) BOOL hidden; @property (nullable, nonatomic, retain) TemporaryHost *host; NS_ASSUME_NONNULL_BEGIN diff --git a/Limelight/Database/TemporaryApp.m b/Limelight/Database/TemporaryApp.m index c4ca2bd..b3de384 100644 --- a/Limelight/Database/TemporaryApp.m +++ b/Limelight/Database/TemporaryApp.m @@ -16,6 +16,7 @@ self.id = app.id; self.name = app.name; self.hdrSupported = app.hdrSupported; + self.hidden = app.hidden; self.host = tempHost; return self; @@ -25,6 +26,7 @@ parent.id = self.id; parent.name = self.name; parent.hdrSupported = self.hdrSupported; + parent.hidden = self.hidden; parent.host = host; } diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.6.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.6.xcdatamodel/contents index 1509045..39de9e9 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.6.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.6.xcdatamodel/contents @@ -2,6 +2,7 @@ + @@ -36,7 +37,7 @@ - + diff --git a/Limelight/UIAppView.h b/Limelight/UIAppView.h index b8466ef..ed55bf3 100644 --- a/Limelight/UIAppView.h +++ b/Limelight/UIAppView.h @@ -12,6 +12,7 @@ @protocol AppCallback - (void) appClicked:(TemporaryApp*) app; +- (void) appLongClicked:(TemporaryApp*) app; @end diff --git a/Limelight/UIAppView.m b/Limelight/UIAppView.m index c831508..967df00 100644 --- a/Limelight/UIAppView.m +++ b/Limelight/UIAppView.m @@ -9,7 +9,7 @@ #import "UIAppView.h" #import "AppAssetManager.h" -static const float REFRESH_CYCLE = 2.0f; +static const float REFRESH_CYCLE = 1.0f; @implementation UIAppView { TemporaryApp* _app; @@ -40,10 +40,15 @@ static UIImage* noImage; self.frame = CGRectMake(0, 0, 150, 200); #endif + [self setAlpha:app.hidden ? 0.4 : 1.0]; + _appImage = [[UIImageView alloc] initWithFrame:self.frame]; [_appImage setImage:noImage]; [self addSubview:_appImage]; + UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(appLongClicked:)]; + [self addGestureRecognizer:longPressRecognizer]; + if (@available(iOS 9.0, tvOS 9.0, *)) { [self addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventPrimaryActionTriggered]; } @@ -78,6 +83,12 @@ static UIImage* noImage; [_callback appClicked:_app]; } +- (void) appLongClicked:(UILongPressGestureRecognizer*)gesture { + if (gesture.state == UIGestureRecognizerStateBegan) { + [_callback appLongClicked:_app]; + } +} + - (void) updateAppImage { if (_appOverlay != nil) { [_appOverlay removeFromSuperview]; @@ -185,6 +196,9 @@ static UIImage* noImage; [self updateAppImage]; } + // Update opacity if neccessary + [self setAlpha:_app.hidden ? 0.4 : 1.0]; + // Stop updating when we detach from our parent view if (self.superview != nil) { [self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE]; diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index 88164da..e322a22 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -40,6 +40,7 @@ @implementation MainFrameViewController { NSOperationQueue* _opQueue; TemporaryHost* _selectedHost; + BOOL _showHiddenApps; NSString* _uniqueId; NSData* _clientCert; DiscoveryManager* _discMan; @@ -208,6 +209,24 @@ static NSMutableSet* hostList; }); } +- (void) updateAppEntry:(TemporaryApp*)app forHost:(TemporaryHost*)host { + DataManager* database = [[DataManager alloc] init]; + NSMutableSet* newHostAppList = [NSMutableSet setWithSet:host.appList]; + + for (TemporaryApp* savedApp in newHostAppList) { + if ([app.id isEqualToString:savedApp.id]) { + savedApp.name = app.name; + savedApp.hdrSupported = app.hdrSupported; + savedApp.hidden = app.hidden; + + host.appList = newHostAppList; + + [database updateAppsForExistingHost:host]; + return; + } + } +} + - (void) updateApplist:(NSSet*) newList forHost:(TemporaryHost*)host { DataManager* database = [[DataManager alloc] init]; NSMutableSet* newHostAppList = [NSMutableSet setWithSet:host.appList]; @@ -218,6 +237,7 @@ static NSMutableSet* hostList; if ([app.id isEqualToString:savedApp.id]) { savedApp.name = app.name; savedApp.hdrSupported = app.hdrSupported; + // Don't propagate hidden, because we want the local data to prevail appAlreadyInList = YES; break; } @@ -275,6 +295,7 @@ static NSMutableSet* hostList; #endif [_appManager stopRetrieving]; + _showHiddenApps = NO; _selectedHost = nil; _sortedAppList = nil; @@ -466,6 +487,12 @@ static NSMutableSet* hostList; [[self activeViewController] presentViewController:wolAlert animated:YES completion:nil]; }]]; } + else if (host.pairState == PairStatePaired) { + [longClickAlert addAction:[UIAlertAction actionWithTitle:@"Show Hidden Apps" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){ + self->_showHiddenApps = YES; + [self hostClicked:host view:view]; + }]]; + } [longClickAlert addAction:[UIAlertAction actionWithTitle:@"Test Network" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { [self showLoadingFrame:^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -642,8 +669,8 @@ static NSMutableSet* hostList; } } -- (void) appClicked:(TemporaryApp *)app { - Log(LOG_D, @"Clicked app: %@", app.name); +- (void) appLongClicked:(TemporaryApp*) app { + Log(LOG_D, @"Long clicked app: %@", app.name); [_appManager stopRetrieving]; @@ -655,18 +682,43 @@ static NSMutableSet* hostList; [[self revealViewController] revealToggleAnimated:NO]; } #endif - + TemporaryApp* currentApp = [self findRunningApp:app.host]; + + NSString* message; + + if (currentApp == nil || [app.id isEqualToString:currentApp.id]) { + if (app.hidden) { + message = @"Hidden"; + } + else { + message = @""; + } + } + else { + message = [NSString stringWithFormat:@"%@ is currently running", currentApp.name]; + } + + UIAlertController* alertController = [UIAlertController + alertControllerWithTitle: app.name + message:message + preferredStyle:UIAlertControllerStyleActionSheet]; + + [alertController addAction:[UIAlertAction + actionWithTitle:currentApp == nil ? @"Launch App" : ([app.id isEqualToString:currentApp.id] ? @"Resume App" : @"Resume Running App") style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){ + if (currentApp != nil) { + Log(LOG_I, @"Resuming application: %@", currentApp.name); + [self prepareToStreamApp:currentApp]; + } + else { + Log(LOG_I, @"Launching application: %@", app.name); + [self prepareToStreamApp:app]; + } + + [self performSegueWithIdentifier:@"createStreamFrame" sender:nil]; + }]]; + if (currentApp != nil) { - UIAlertController* alertController = [UIAlertController - alertControllerWithTitle: app.name - message: [app.id isEqualToString:currentApp.id] ? @"" : [NSString stringWithFormat:@"%@ is currently running", currentApp.name]preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction - actionWithTitle:[app.id isEqualToString:currentApp.id] ? @"Resume App" : @"Resume Running App" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){ - Log(LOG_I, @"Resuming application: %@", currentApp.name); - [self prepareToStreamApp:currentApp]; - [self performSegueWithIdentifier:@"createStreamFrame" sender:nil]; - }]]; [alertController addAction:[UIAlertAction actionWithTitle: [app.id isEqualToString:currentApp.id] ? @"Quit App" : @"Quit Running App and Start" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action){ Log(LOG_I, @"Quitting application: %@", currentApp.name); @@ -713,9 +765,6 @@ static NSMutableSet* hostList; else { app.host.currentGame = @"0"; dispatch_async(dispatch_get_main_queue(), ^{ - // Refresh the UI - [self updateAppsForHost:app.host]; - // If it succeeds and we're to start streaming, segue to the stream if (![app.id isEqualToString:currentApp.id]) { [self prepareToStreamApp:app]; @@ -733,8 +782,39 @@ static NSMutableSet* hostList; }]; }]]; - [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; - [[self activeViewController] presentViewController:alertController animated:YES completion:nil]; + } + + if (currentApp == nil || ![app.id isEqualToString:currentApp.id] || app.hidden) { + [alertController addAction:[UIAlertAction actionWithTitle:app.hidden ? @"Show App" : @"Hide App" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { + app.hidden = !app.hidden; + [self updateAppEntry:app forHost:app.host]; + + // Don't call updateAppsForHost because that will nuke this + // app immediately if we're not showing hidden apps. + }]]; + } + + [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + [[self activeViewController] presentViewController:alertController animated:YES completion:nil]; +} + +- (void) appClicked:(TemporaryApp *)app { + Log(LOG_D, @"Clicked app: %@", app.name); + + [_appManager stopRetrieving]; + +#if !TARGET_OS_TV + if (currentPosition != FrontViewPositionLeft) { + // This must not be animated because we need the position + // to change (and notify our callback to save settings data) + // before we call prepareToStreamApp. + [[self revealViewController] revealToggleAnimated:NO]; + } +#endif + + if ([self findRunningApp:app.host]) { + // If there's a running app, display a menu + [self appLongClicked:app]; } else { [self prepareToStreamApp:app]; [self performSegueWithIdentifier:@"createStreamFrame" sender:nil]; @@ -1188,6 +1268,16 @@ static NSMutableSet* hostList; _sortedAppList = [host.appList allObjects]; _sortedAppList = [_sortedAppList sortedArrayUsingSelector:@selector(compareName:)]; + if (!_showHiddenApps) { + NSMutableArray* visibleAppList = [NSMutableArray array]; + for (TemporaryApp* app in _sortedAppList) { + if (!app.hidden) { + [visibleAppList addObject:app]; + } + } + _sortedAppList = visibleAppList; + } + [hostScrollView removeFromSuperview]; [self.collectionView reloadData]; }