Browse Source

Add window and scene managemenet screen

Also fix potential bugs in FLEXExplorerViewController
Tanner Bennett 6 years ago
parent
commit
c056a29375

+ 15 - 0
Classes/ExplorerInterface/FLEXExplorerViewController.m

@@ -19,6 +19,7 @@
 #import "FLEXObjectExplorerFactory.h"
 #import "FLEXNetworkMITMViewController.h"
 #import "FLEXTabsViewController.h"
+#import "FLEXWindowManagerController.h"
 
 static NSString *const kFLEXToolbarTopMarginDefaultsKey = @"com.flex.FLEXToolbar.topMargin";
 
@@ -440,6 +441,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
     [toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
         initWithTarget:self action:@selector(handleToolbarShowTabsGesture:)
     ]];
+    
+    // Long press gesture to present window manager
+    [toolbar.selectItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
+        initWithTarget:self action:@selector(handleToolbarWindowManagerGesture:)
+    ]];
 }
 
 - (void)handleToolbarPanGesture:(UIPanGestureRecognizer *)panGR {
@@ -523,6 +529,15 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
     ] animated:YES completion:nil];
 }
 
+- (void)handleToolbarWindowManagerGesture:(UILongPressGestureRecognizer *)sender {
+    // Back up the UIMenuController items since dismissViewController: will attempt to replace them
+    self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
+    
+    [super presentViewController:[[UINavigationController alloc]
+        initWithRootViewController:[FLEXWindowManagerController new]
+    ] animated:YES completion:nil];
+}
+
 
 #pragma mark - View Selection
 

+ 17 - 0
Classes/ExplorerInterface/FLEXWindowManagerController.h

@@ -0,0 +1,17 @@
+//
+//  FLEXWindowManagerController.h
+//  FLEX
+//
+//  Created by Tanner on 2/6/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXTableViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLEXWindowManagerController : FLEXTableViewController
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 303 - 0
Classes/ExplorerInterface/FLEXWindowManagerController.m

@@ -0,0 +1,303 @@
+//
+//  FLEXWindowManagerController.m
+//  FLEX
+//
+//  Created by Tanner on 2/6/20.
+//  Copyright © 2020 Flipboard. All rights reserved.
+//
+
+#import "FLEXWindowManagerController.h"
+#import "FLEXManager+Private.h"
+#import "FLEXUtility.h"
+
+@interface FLEXWindowManagerController ()
+@property (nonatomic) UIWindow *keyWindow;
+@property (nonatomic, copy) NSString *keyWindowSubtitle;
+@property (nonatomic, copy) NSArray<UIWindow *> *windows;
+@property (nonatomic, copy) NSArray<NSString *> *windowSubtitles;
+@property (nonatomic, copy) NSArray<UIScene *> *scenes API_AVAILABLE(ios(13));
+@property (nonatomic, copy) NSArray<NSString *> *sceneSubtitles;
+@end
+
+@implementation FLEXWindowManagerController
+
+#pragma mark - Initialization
+
+- (id)init {
+    return [self initWithStyle:UITableViewStylePlain];
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    self.title = @"Windows";
+    if (@available(iOS 13, *)) {
+        self.title = @"Windows and Scenes";
+    }
+    
+    [self disableToolbar];
+    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
+        initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissAnimated)
+    ];
+    
+    [self reloadData];
+}
+
+
+#pragma mark - Private
+
+- (void)reloadData {
+    self.keyWindow = UIApplication.sharedApplication.keyWindow;
+    self.windows = UIApplication.sharedApplication.windows;
+    self.windowSubtitles = [self.windows flex_mapped:^id(UIWindow *window, NSUInteger idx) {
+        return [NSString stringWithFormat:@"Level: %@ — Root: %@",
+            @(window.windowLevel), window.rootViewController
+        ];
+    }];
+    self.keyWindowSubtitle = self.windowSubtitles[[self.windows indexOfObject:self.keyWindow]];
+    if (@available(iOS 13, *)) {
+        self.scenes = UIApplication.sharedApplication.connectedScenes.allObjects;
+        self.sceneSubtitles = [self.scenes flex_mapped:^id(UIScene *scene, NSUInteger idx) {
+            return [self sceneDescription:scene];
+        }];
+    }
+}
+
+- (void)dismissAnimated {
+    [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)showRevertOrDismissAlert:(void(^)())revertBlock {
+    [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
+    [self reloadData];
+    [self.tableView reloadData];
+    
+    UIWindow *highestWindow = UIApplication.sharedApplication.keyWindow;
+    UIWindowLevel maxLevel = 0;
+    for (UIWindow *window in UIApplication.sharedApplication.windows) {
+        if (window.windowLevel > maxLevel) {
+            maxLevel = window.windowLevel;
+            highestWindow = window;
+        }
+    }
+    
+    [FLEXAlert makeAlert:^(FLEXAlert *make) {
+        make.title(@"Keep Changes?");
+        make.message(@"If you do not wish to keep these settings, choose 'Revert Changes' below.");
+        
+        make.button(@"Keep Changes").destructiveStyle();
+        make.button(@"Keep Changes and Dismiss").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
+            [self dismissAnimated];
+        });
+        make.button(@"Revert Changes").cancelStyle().handler(^(NSArray<NSString *> *strings) {
+            revertBlock();
+            [self reloadData];
+            [self.tableView reloadData];
+        });
+    } showFrom:[FLEXUtility topViewControllerInWindow:highestWindow]];
+}
+
+- (NSString *)sceneDescription:(UIScene *)scene API_AVAILABLE(ios(13)) {
+    NSString *state = [self stringFromSceneState:scene.activationState];
+    NSString *title = scene.title.length ? scene.title : nil;
+    NSString *suffix = nil;
+    
+    if ([scene isKindOfClass:[UIWindowScene class]]) {
+        UIWindowScene *windowScene = (id)scene;
+        suffix = FLEXPluralString(windowScene.windows.count, @"windows", @"window");
+    }
+    
+    NSMutableString *description = state.mutableCopy;
+    if (title) {
+        [description appendFormat:@" — %@", title];
+    }
+    if (suffix) {
+        [description appendFormat:@" — %@", suffix];
+    }
+    
+    return description.copy;
+}
+
+- (NSString *)stringFromSceneState:(UISceneActivationState)state API_AVAILABLE(ios(13)) {
+    switch (state) {
+        case UISceneActivationStateUnattached:
+            return @"Unattached";
+        case UISceneActivationStateForegroundActive:
+            return @"Active";
+        case UISceneActivationStateForegroundInactive:
+            return @"Inactive";
+        case UISceneActivationStateBackground:
+            return @"Backgrounded";
+    }
+    
+    return [NSString stringWithFormat:@"Unknown state: %@", @(state)];
+}
+
+
+#pragma mark - Table View Data Source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    if (@available(iOS 13, *)) {
+        return 3;
+    }
+    
+    return 2;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    switch (section) {
+        case 0:
+            return 1;
+        case 1:
+            return self.windows.count;
+        case 2:
+            if (@available(iOS 13, *)) {
+                return self.scenes.count;
+            }
+    }
+    
+    @throw NSInternalInconsistencyException;
+    return 0;
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
+    switch (section) {
+        case 0: return @"Key Window";
+        case 1: return @"Windows";
+        case 2: return @"Connected Scenes";
+    }
+    
+    return nil;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXDetailCell forIndexPath:indexPath];
+    UIWindow *window = nil;
+    NSString *subtitle = nil;
+    
+    switch (indexPath.section) {
+        case 0:
+            window = self.keyWindow;
+            subtitle = self.keyWindowSubtitle;
+            break;
+        case 1:
+            window = self.windows[indexPath.row];
+            subtitle = self.windowSubtitles[indexPath.row];
+            break;
+        case 2:
+            if (@available(iOS 13, *)) {
+                UIScene *scene = self.scenes[indexPath.row];
+                cell.textLabel.text = scene.description;
+                cell.detailTextLabel.text = self.sceneSubtitles[indexPath.row];
+                return cell;
+            }
+    }
+    
+    cell.textLabel.text = window.description;
+    cell.detailTextLabel.text = [NSString
+        stringWithFormat:@"Level: %@ — Root: %@", @(window.windowLevel), window.rootViewController
+    ];
+    
+    return cell;
+}
+
+
+#pragma mark - Table View Delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    UIWindow *window = nil;
+    NSString *subtitle = nil;
+    FLEXWindow *flex = FLEXManager.sharedManager.explorerWindow;
+    
+    id cancelHandler = ^{
+        [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
+    };
+    
+    switch (indexPath.section) {
+        case 0:
+            window = self.keyWindow;
+            subtitle = self.keyWindowSubtitle;
+            break;
+        case 1:
+            window = self.windows[indexPath.row];
+            subtitle = self.windowSubtitles[indexPath.row];
+            break;
+        case 2:
+            if (@available(iOS 13, *)) {
+                UIScene *scene = self.scenes[indexPath.row];
+                UIWindowScene *oldScene = flex.windowScene;
+                BOOL isWindowScene = [scene isKindOfClass:[UIWindowScene class]];
+                BOOL isFLEXScene = isWindowScene ? flex.windowScene == scene : NO;
+                
+                [FLEXAlert makeAlert:^(FLEXAlert *make) {
+                    make.title(NSStringFromClass(scene.class));
+                    
+                    if (isWindowScene) {
+                        if (isFLEXScene) {
+                            make.message(@"Already the FLEX window scene");
+                        }
+                        
+                        make.button(@"Set as FLEX Window Scene")
+                        .handler(^(NSArray<NSString *> *strings) {
+                            flex.windowScene = (id)scene;
+                            [self showRevertOrDismissAlert:^{
+                                flex.windowScene = oldScene;
+                            }];
+                        }).enabled(!isFLEXScene);
+                        make.button(@"Cancel").cancelStyle();
+                    } else {
+                        make.message(@"Not a UIWindowScene");
+                        make.button(@"Dismiss").cancelStyle().handler(cancelHandler);
+                    }
+                } showFrom:self];
+            }
+    }
+
+    __block UIWindow *targetWindow = nil, *oldKeyWindow = nil;
+    __block UIWindowLevel oldLevel;
+    __block BOOL wasVisible;
+    
+    subtitle = [subtitle stringByAppendingString:
+        @"\n\n1) Adjust the FLEX window level relative to this window,\n"
+        "2) adjust this window's level relative to the FLEX window,\n"
+        "3) set this window's level to a specific value, or\n"
+        "4) make this window the key window if it isn't already."
+    ];
+    
+    [FLEXAlert makeAlert:^(FLEXAlert *make) {
+        make.title(NSStringFromClass(window.class)).message(subtitle);
+        make.button(@"Adjust FLEX Window Level").handler(^(NSArray<NSString *> *strings) {
+            targetWindow = flex; oldLevel = flex.windowLevel;
+            flex.windowLevel = window.windowLevel + strings.firstObject.integerValue;
+            
+            [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
+        });
+        make.button(@"Adjust This Window's Level").handler(^(NSArray<NSString *> *strings) {
+            targetWindow = window; oldLevel = window.windowLevel;
+            window.windowLevel = flex.windowLevel + strings.firstObject.integerValue;
+            
+            [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
+        });
+        make.button(@"Set This Window's Level").handler(^(NSArray<NSString *> *strings) {
+            targetWindow = window; oldLevel = window.windowLevel;
+            window.windowLevel = strings.firstObject.integerValue;
+            
+            [self showRevertOrDismissAlert:^{ targetWindow.windowLevel = oldLevel; }];
+        });
+        make.button(@"Make Key And Visible").handler(^(NSArray<NSString *> *strings) {
+            oldKeyWindow = UIApplication.sharedApplication.keyWindow;
+            wasVisible = window.hidden;
+            [window makeKeyAndVisible];
+            
+            [self showRevertOrDismissAlert:^{
+                window.hidden = wasVisible;
+                [oldKeyWindow makeKeyWindow];
+            }];
+        }).enabled(!window.isKeyWindow && !window.hidden);
+        make.button(@"Cancel").cancelStyle().handler(cancelHandler);
+        
+        make.textField(@"+/- window level, i.e. 5 or -10");
+    } showFrom:self];
+}
+
+@end

+ 1 - 5
Classes/Manager/FLEXManager+Extensibility.m

@@ -221,11 +221,7 @@
 }
 
 - (UIViewController *)topViewController {
-    UIViewController *topViewController = UIApplication.sharedApplication.keyWindow.rootViewController;
-    while (topViewController.presentedViewController) {
-        topViewController = topViewController.presentedViewController;
-    }
-    return topViewController;
+    return [FLEXUtility topViewControllerInWindow:UIApplication.sharedApplication.keyWindow];
 }
 
 - (void)toggleTopViewControllerOfClass:(Class)class {

+ 2 - 1
Classes/Manager/FLEXManager+Private.h

@@ -7,12 +7,13 @@
 //
 
 #import "FLEXManager.h"
+#import "FLEXWindow.h"
 
 @class FLEXGlobalsEntry, FLEXExplorerViewController;
 
 @interface FLEXManager (Private)
 
-//@property (nonatomic) FLEXWindow *explorerWindow;
+@property (nonatomic, readonly) FLEXWindow *explorerWindow;
 @property (nonatomic, readonly) FLEXExplorerViewController *explorerViewController;
 
 /// An array of FLEXGlobalsEntry objects that have been registered by the user.

+ 2 - 0
Classes/Utility/FLEXUtility.h

@@ -81,6 +81,8 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
 @property (nonatomic, readonly, class) UIWindow *appKeyWindow;
 /// The first active \c UIWindowScene of the app.
 @property (nonatomic, readonly, class) UIWindowScene *activeScene API_AVAILABLE(ios(13.0));
+/// @return top-most view controller of the given window
++ (UIViewController *)topViewControllerInWindow:(UIWindow *)window;
 
 + (UIColor *)consistentRandomColorForObject:(id)object;
 + (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame;

+ 8 - 0
Classes/Utility/FLEXUtility.m

@@ -59,6 +59,14 @@
 }
 #endif
 
++ (UIViewController *)topViewControllerInWindow:(UIWindow *)window {
+    UIViewController *topViewController = window.rootViewController;
+    while (topViewController.presentedViewController) {
+        topViewController = topViewController.presentedViewController;
+    }
+    return topViewController;
+}
+
 + (UIColor *)consistentRandomColorForObject:(id)object {
     CGFloat hue = (((NSUInteger)object >> 4) % 256) / 255.0;
     return [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0];

+ 8 - 0
FLEX.xcodeproj/project.pbxproj

@@ -146,6 +146,8 @@
 		C312A13123ECB5D300E38049 /* FLEXBookmarkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C312A12F23ECB5D300E38049 /* FLEXBookmarkManager.m */; };
 		C312A13423ECBE5800E38049 /* FLEXBookmarksViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C312A13223ECBE5800E38049 /* FLEXBookmarksViewController.h */; };
 		C312A13523ECBE5800E38049 /* FLEXBookmarksViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C312A13323ECBE5800E38049 /* FLEXBookmarksViewController.m */; };
+		C312A13C23ECE79000E38049 /* FLEXWindowManagerController.m in Sources */ = {isa = PBXBuildFile; fileRef = C312A13A23ECE79000E38049 /* FLEXWindowManagerController.m */; };
+		C312A13D23ECE79000E38049 /* FLEXWindowManagerController.h in Headers */ = {isa = PBXBuildFile; fileRef = C312A13B23ECE79000E38049 /* FLEXWindowManagerController.h */; };
 		C31C4A6923342A2200C35F12 /* FLEXMetadataSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */; };
 		C31C4A6A23342A2200C35F12 /* FLEXMetadataSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */; };
 		C31D93E423E38CBE005517BF /* FLEXBlockShortcuts.h in Headers */ = {isa = PBXBuildFile; fileRef = C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */; };
@@ -477,6 +479,8 @@
 		C312A12F23ECB5D300E38049 /* FLEXBookmarkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBookmarkManager.m; sourceTree = "<group>"; };
 		C312A13223ECBE5800E38049 /* FLEXBookmarksViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBookmarksViewController.h; sourceTree = "<group>"; };
 		C312A13323ECBE5800E38049 /* FLEXBookmarksViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBookmarksViewController.m; sourceTree = "<group>"; };
+		C312A13A23ECE79000E38049 /* FLEXWindowManagerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXWindowManagerController.m; sourceTree = "<group>"; };
+		C312A13B23ECE79000E38049 /* FLEXWindowManagerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXWindowManagerController.h; sourceTree = "<group>"; };
 		C31C4A6723342A2200C35F12 /* FLEXMetadataSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXMetadataSection.h; sourceTree = "<group>"; };
 		C31C4A6823342A2200C35F12 /* FLEXMetadataSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXMetadataSection.m; sourceTree = "<group>"; };
 		C31D93E223E38CBE005517BF /* FLEXBlockShortcuts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBlockShortcuts.h; sourceTree = "<group>"; };
@@ -989,6 +993,8 @@
 				94A5151C1C4CA1F10063292F /* FLEXWindow.m */,
 				94A515191C4CA1F10063292F /* FLEXExplorerViewController.h */,
 				94A5151A1C4CA1F10063292F /* FLEXExplorerViewController.m */,
+				C312A13B23ECE79000E38049 /* FLEXWindowManagerController.h */,
+				C312A13A23ECE79000E38049 /* FLEXWindowManagerController.m */,
 			);
 			path = ExplorerInterface;
 			sourceTree = "<group>";
@@ -1391,6 +1397,7 @@
 				C31C4A6923342A2200C35F12 /* FLEXMetadataSection.h in Headers */,
 				C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */,
 				94A5151F1C4CA1F10063292F /* FLEXWindow.h in Headers */,
+				C312A13D23ECE79000E38049 /* FLEXWindowManagerController.h in Headers */,
 				779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */,
 				C32A19622317378C00EB02AC /* FLEXDefaultsContentSection.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
@@ -1691,6 +1698,7 @@
 				C3F31D442267D883003C991A /* FLEXTableView.m in Sources */,
 				3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m in Sources */,
 				224D49AB1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.m in Sources */,
+				C312A13C23ECE79000E38049 /* FLEXWindowManagerController.m in Sources */,
 				C36FBFDA230F3B98008D95D5 /* FLEXPropertyAttributes.m in Sources */,
 				779B1ED91C0C4D7C001F5E49 /* FLEXTableLeftCell.m in Sources */,
 				3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,