|
@@ -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
|