diff --git a/Classes/Core/Controllers/FLEXFilteringTableViewController.h b/Classes/Core/Controllers/FLEXFilteringTableViewController.h index f0488a17d2..3573924b43 100644 --- a/Classes/Core/Controllers/FLEXFilteringTableViewController.h +++ b/Classes/Core/Controllers/FLEXFilteringTableViewController.h @@ -35,7 +35,7 @@ #pragma mark - FLEXFilteringTableViewController /// A table view which implements \c UITableView* methods using arrays of -/// \c FLEXTableViewSection objects provied by a special delegate. +/// \c FLEXTableViewSection objects provided by a special delegate. @interface FLEXFilteringTableViewController : FLEXTableViewController /// Stores the current search query. diff --git a/Classes/ExplorerInterface/FLEXExplorerViewController.m b/Classes/ExplorerInterface/FLEXExplorerViewController.m index 9b4964dd43..2df05ffdb3 100644 --- a/Classes/ExplorerInterface/FLEXExplorerViewController.m +++ b/Classes/ExplorerInterface/FLEXExplorerViewController.m @@ -21,6 +21,8 @@ #import "FLEXWindowManagerController.h" #import "FLEXViewControllersViewController.h" #import "NSUserDefaults+FLEX.h" +#import "FLEXManager+Extensibility.h" +#import "FLEXUserGlobalEntriesContainer+Private.h" typedef NS_ENUM(NSUInteger, FLEXExplorerMode) { FLEXExplorerModeDefault, @@ -1030,7 +1032,10 @@ - (void)toggleViewsToolWithCompletion:(void(^)(void))completion { - (void)toggleMenuTool { [self toggleToolWithViewControllerProvider:^UINavigationController *{ - return [FLEXNavigationController withRootViewController:[FLEXGlobalsViewController new]]; + FLEXGlobalsViewController *controller = [FLEXGlobalsViewController new]; + controller.customEntries = FLEXManager.sharedManager.globalEntriesContainer.entries; + controller.showsDefaultEntries = YES; + return [FLEXNavigationController withRootViewController:controller]; } completion:nil]; } diff --git a/Classes/ExplorerInterface/Tabs/FLEXTabsViewController.m b/Classes/ExplorerInterface/Tabs/FLEXTabsViewController.m index 9f220461e5..a08c1b090b 100644 --- a/Classes/ExplorerInterface/Tabs/FLEXTabsViewController.m +++ b/Classes/ExplorerInterface/Tabs/FLEXTabsViewController.m @@ -17,6 +17,8 @@ #import "FLEXExplorerViewController.h" #import "FLEXGlobalsViewController.h" #import "FLEXBookmarksViewController.h" +#import "FLEXManager+Extensibility.h" +#import "FLEXUserGlobalEntriesContainer+Private.h" @interface FLEXTabsViewController () @property (nonatomic, copy) NSArray *openTabs; @@ -198,8 +200,12 @@ - (void)addTabButtonPressed:(UIBarButtonItem *)sender { [FLEXAlert makeSheet:^(FLEXAlert *make) { make.title(@"New Tab"); make.button(@"Main Menu").handler(^(NSArray *strings) { + FLEXGlobalsViewController *controller = [FLEXGlobalsViewController new]; + controller.customEntries = FLEXManager.sharedManager.globalEntriesContainer.entries; + controller.showsDefaultEntries = YES; + [self addTabAndDismiss:[FLEXNavigationController - withRootViewController:[FLEXGlobalsViewController new] + withRootViewController:controller ]]; }); make.button(@"Choose from Bookmarks").handler(^(NSArray *strings) { @@ -210,9 +216,13 @@ - (void)addTabButtonPressed:(UIBarButtonItem *)sender { make.button(@"Cancel").cancelStyle(); } showFrom:self source:sender]; } else { + FLEXGlobalsViewController *controller = [FLEXGlobalsViewController new]; + controller.customEntries = FLEXManager.sharedManager.globalEntriesContainer.entries; + controller.showsDefaultEntries = YES; + // No bookmarks, just open the main menu [self addTabAndDismiss:[FLEXNavigationController - withRootViewController:[FLEXGlobalsViewController new] + withRootViewController:controller ]]; } } diff --git a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h index c7866c05c2..69884496f6 100644 --- a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h +++ b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h @@ -10,6 +10,8 @@ NS_ASSUME_NONNULL_BEGIN +@class FLEXUserGlobalEntriesContainer; + typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) { FLEXGlobalsRowProcessInfo, FLEXGlobalsRowNetworkHistory, @@ -51,8 +53,10 @@ typedef UIViewController * _Nullable (^FLEXGlobalsEntryViewControllerFuture)(voi /// Do something like present an alert, then use the host /// view controller to present or push another view controller. typedef void (^FLEXGlobalsEntryRowAction)(__kindof UITableViewController * _Nonnull host); +/// Use the container to register nested global entries. +typedef void (^FLEXNestedGlobalEntriesHandler)(FLEXUserGlobalEntriesContainer * _Nonnull container); -/// For view controllers to conform to to indicate they support being used +/// For view controllers to conform and to indicate they support being used /// in the globals table view controller. These methods help create concrete entries. /// /// Previously, the concrete entries relied on "futures" for the view controller and title. @@ -81,15 +85,20 @@ typedef void (^FLEXGlobalsEntryRowAction)(__kindof UITableViewController * _Nonn @interface FLEXGlobalsEntry : NSObject @property (nonatomic, readonly, nonnull) FLEXGlobalsEntryNameFuture entryNameFuture; +@property (nonatomic, readonly) UITableViewCellAccessoryType cellAccessoryType; @property (nonatomic, readonly, nullable) FLEXGlobalsEntryViewControllerFuture viewControllerFuture; @property (nonatomic, readonly, nullable) FLEXGlobalsEntryRowAction rowAction; -+ (instancetype)entryWithEntry:(Class)entry row:(FLEXGlobalsRow)row; ++ (instancetype)entryWithEntry:(Class)entry + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + row:(FLEXGlobalsRow)row; + (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType viewControllerFuture:(FLEXGlobalsEntryViewControllerFuture)viewControllerFuture; + (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType action:(FLEXGlobalsEntryRowAction)rowSelectedAction; @end diff --git a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.m b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.m index b93b21422f..04e6f88574 100644 --- a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.m +++ b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.m @@ -10,7 +10,9 @@ @implementation FLEXGlobalsEntry -+ (instancetype)entryWithEntry:(Class)cls row:(FLEXGlobalsRow)row { ++ (instancetype)entryWithEntry:(Class)cls + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + row:(FLEXGlobalsRow)row { BOOL providesVCs = [cls respondsToSelector:@selector(globalsEntryViewController:)]; BOOL providesActions = [cls respondsToSelector:@selector(globalsEntryRowAction:)]; NSParameterAssert(cls); @@ -18,6 +20,7 @@ + (instancetype)entryWithEntry:(Class)cls row:(FLEXGlobalsRow) FLEXGlobalsEntry *entry = [self new]; entry->_entryNameFuture = ^{ return [cls globalsEntryTitle:row]; }; + entry->_cellAccessoryType = cellAccessoryType; if (providesVCs) { id action = providesActions ? [cls globalsEntryRowAction:row] : nil; @@ -34,24 +37,28 @@ + (instancetype)entryWithEntry:(Class)cls row:(FLEXGlobalsRow) } + (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType viewControllerFuture:(FLEXGlobalsEntryViewControllerFuture)viewControllerFuture { NSParameterAssert(nameFuture); NSParameterAssert(viewControllerFuture); FLEXGlobalsEntry *entry = [self new]; entry->_entryNameFuture = [nameFuture copy]; + entry->_cellAccessoryType = cellAccessoryType; entry->_viewControllerFuture = [viewControllerFuture copy]; return entry; } + (instancetype)entryWithNameFuture:(FLEXGlobalsEntryNameFuture)nameFuture + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType action:(FLEXGlobalsEntryRowAction)rowSelectedAction { NSParameterAssert(nameFuture); NSParameterAssert(rowSelectedAction); FLEXGlobalsEntry *entry = [self new]; entry->_entryNameFuture = [nameFuture copy]; + entry->_cellAccessoryType = cellAccessoryType; entry->_rowAction = [rowSelectedAction copy]; return entry; @@ -77,7 +84,10 @@ @implementation NSObject (FLEXGlobalsEntry) + (FLEXGlobalsEntry *)flex_concreteGlobalsEntry:(FLEXGlobalsRow)row { if ([self conformsToProtocol:@protocol(FLEXGlobalsEntry)]) { - return [FLEXGlobalsEntry entryWithEntry:self row:row]; + return [FLEXGlobalsEntry entryWithEntry:self + cellAccessoryType:UITableViewCellAccessoryDisclosureIndicator + row:row + ]; } return nil; diff --git a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsSection.m b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsSection.m index abb97ea2b4..c3548f6238 100644 --- a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsSection.m +++ b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsSection.m @@ -69,7 +69,7 @@ - (UIViewController *)viewControllerToPushForRow:(NSInteger)row { } - (void)configureCell:(__kindof UITableViewCell *)cell forRow:(NSInteger)row { - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.accessoryType = self.rows[row].cellAccessoryType; cell.textLabel.font = UIFont.flex_defaultTableCellFont; cell.textLabel.text = self.rows[row].entryNameFuture(); } diff --git a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.h b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.h index b4b79b8c68..6f9016b6e6 100644 --- a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.h +++ b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.h @@ -7,6 +7,7 @@ // #import "FLEXFilteringTableViewController.h" +@class FLEXGlobalsEntry; @protocol FLEXGlobalsTableViewControllerDelegate; typedef NS_ENUM(NSUInteger, FLEXGlobalsSectionKind) { @@ -25,4 +26,8 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsSectionKind) { @interface FLEXGlobalsViewController : FLEXFilteringTableViewController +@property (nonatomic, nullable) NSString *customTitle; +@property (nonatomic, nonnull) NSArray *customEntries; +@property (nonatomic) BOOL showsDefaultEntries; + @end diff --git a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.m b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.m index 569b5633df..8a632c1b1b 100644 --- a/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.m +++ b/Classes/GlobalStateExplorers/Globals/FLEXGlobalsViewController.m @@ -19,6 +19,7 @@ #import "FLEXCookiesViewController.h" #import "FLEXGlobalsEntry.h" #import "FLEXManager+Private.h" +#import "FLEXUserGlobalEntriesContainer+Private.h" #import "FLEXSystemLogViewController.h" #import "FLEXNetworkMITMViewController.h" #import "FLEXAddressExplorerCoordinator.h" @@ -97,10 +98,10 @@ + (FLEXGlobalsEntry *)globalsEntryForRow:(FLEXGlobalsRow)row { case FLEXGlobalsRowMainThread: case FLEXGlobalsRowOperationQueue: return [FLEXObjectExplorerFactory flex_concreteGlobalsEntry:row]; - + case FLEXGlobalsRowCount: break; } - + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Missing globals case in switch" userInfo:nil @@ -157,7 +158,7 @@ + (FLEXGlobalsEntry *)globalsEntryForRow:(FLEXGlobalsRow)row { [sections addObject:[FLEXGlobalsSection title:title rows:rowsBySection[@(i)]]]; } }); - + return sections; } @@ -167,19 +168,19 @@ + (FLEXGlobalsEntry *)globalsEntryForRow:(FLEXGlobalsRow)row { - (void)viewDidLoad { [super viewDidLoad]; - self.title = @"💪 FLEX"; + self.title = self.customTitle ?: @"💪 FLEX"; self.showsSearchBar = YES; self.searchBarDebounceInterval = kFLEXDebounceInstant; - self.navigationItem.backBarButtonItem = [UIBarButtonItem flex_backItemWithTitle:@"Back"]; - + self.navigationItem.backBarButtonItem = [UIBarButtonItem flex_backItemWithTitle:self.showsDefaultEntries ? @"Back" : self.title]; + _manuallyDeselectOnAppear = NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 10; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - + [self disableToolbar]; - + if (self.manuallyDeselectOnAppear) { [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES]; } @@ -188,16 +189,18 @@ - (void)viewWillAppear:(BOOL)animated { - (NSArray *)makeSections { NSMutableArray *sections = [NSMutableArray array]; // Do we have custom sections to add? - if (FLEXManager.sharedManager.userGlobalEntries.count) { + if (self.customEntries.count) { NSString *title = [[self class] globalsTitleForSection:FLEXGlobalsSectionCustom]; FLEXGlobalsSection *custom = [FLEXGlobalsSection title:title - rows:FLEXManager.sharedManager.userGlobalEntries + rows:self.customEntries ]; [sections addObject:custom]; } - [sections addObjectsFromArray:[self.class defaultGlobalSections]]; + if (self.showsDefaultEntries) { + [sections addObjectsFromArray:[self.class defaultGlobalSections]]; + } return sections; } diff --git a/Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.h b/Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.h new file mode 100644 index 0000000000..fe15ad5bca --- /dev/null +++ b/Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.h @@ -0,0 +1,121 @@ +// +// FLEXUserGlobalEntriesContainer.h +// FLEX +// +// Created by Iulian Onofrei on 2023-02-10. +// Copyright © 2023 FLEX Team. All rights reserved. +// + +#import + +#import "FLEXGlobalsEntry.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FLEXUserGlobalEntriesContainer : NSObject + +/// Adds an entry at the top of the list of Global State items. +/// Call this method before this view controller is displayed. +/// @param entryName The string to be displayed in the cell. +/// @param objectFutureBlock When you tap on the row, information about the object returned +/// by this block will be displayed. Passing a block that returns an object allows you to display +/// information about an object whose actual pointer may change at runtime (e.g. +currentUser) +/// @note This method must be called from the main thread. +/// The objectFutureBlock will be invoked from the main thread and may return nil. +/// @note The passed block will be copied and retain for the duration of the application, +/// you may want to use __weak references. +- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock; + +/// Adds an entry at the top of the list of Global State items. +/// Call this method before this view controller is displayed. +/// @param entryName The string to be displayed in the cell. +/// @param cellAccessoryType The accessory type to be used for the cell. +/// @param objectFutureBlock When you tap on the row, information about the object returned +/// by this block will be displayed. Passing a block that returns an object allows you to display +/// information about an object whose actual pointer may change at runtime (e.g. +currentUser) +/// @note This method must be called from the main thread. +/// The objectFutureBlock will be invoked from the main thread and may return nil. +/// @note The passed block will be copied and retain for the duration of the application, +/// you may want to use __weak references. +- (void)registerGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + objectFutureBlock:(id (^)(void))objectFutureBlock; + +/// Adds an entry at the top of the list of Global State items. +/// Call this method before this view controller is displayed. +/// @param entryName The string to be displayed in the cell. +/// @param viewControllerFutureBlock When you tap on the row, view controller returned +/// by this block will be pushed on the navigation controller stack. +/// @note This method must be called from the main thread. +/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil. +/// @note The passed block will be copied and retain for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerGlobalEntryWithName:(NSString *)entryName + viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock; + +/// Adds an entry at the top of the list of Global State items. +/// Call this method before this view controller is displayed. +/// @param entryName The string to be displayed in the cell. +/// @param cellAccessoryType The accessory type to be used for the cell. +/// @param viewControllerFutureBlock When you tap on the row, view controller returned +/// by this block will be pushed on the navigation controller stack. +/// @note This method must be called from the main thread. +/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil. +/// @note The passed block will be copied and retain for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock; + +/// Adds an entry at the top of the list of Global State items. +/// @param entryName The string to be displayed in the cell. +/// @param rowSelectedAction When you tap on the row, this block will be invoked +/// with the host table view view controller. Use it to deselect the row or present an alert. +/// @note This method must be called from the main thread. +/// The rowSelectedAction will be invoked from the main thread. +/// @note The passed block will be copied and retained for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction; + +/// Adds an entry at the top of the list of Global State items. +/// @param entryName The string to be displayed in the cell. +/// @param cellAccessoryType The accessory type to be used for the cell. +/// @param rowSelectedAction When you tap on the row, this block will be invoked +/// with the host table view view controller. Use it to deselect the row or present an alert. +/// @note This method must be called from the main thread. +/// The rowSelectedAction will be invoked from the main thread. +/// @note The passed block will be copied and retained for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + action:(FLEXGlobalsEntryRowAction)rowSelectedAction; + +/// Adds an entry at the top of the list of Global State items. +/// @param entryName The string to be displayed in the cell. +/// @param nestedEntriesHandler When you tap on the row, this block will be invoked +/// with the container object. Use it to register nested entries. +/// @note This method must be called from the main thread. +/// The nestedEntriesHandler will be invoked from the main thread. +/// @note The passed block will be copied and retained for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerNestedGlobalEntryWithName:(NSString *)entryName handler:(FLEXNestedGlobalEntriesHandler)nestedEntriesHandler; + +/// Adds an entry at the top of the list of Global State items. +/// @param entryName The string to be displayed in the cell. +/// @param cellAccessoryType The accessory type to be used for the cell. +/// @param nestedEntriesHandler When you tap on the row, this block will be invoked +/// with the container object. Use it to register nested entries. +/// @note This method must be called from the main thread. +/// The nestedEntriesHandler will be invoked from the main thread. +/// @note The passed block will be copied and retained for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerNestedGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + handler:(FLEXNestedGlobalEntriesHandler)nestedEntriesHandler; + +/// Removes all registered global entries. +- (void)clearGlobalEntries; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.m b/Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.m new file mode 100644 index 0000000000..2f519086d1 --- /dev/null +++ b/Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.m @@ -0,0 +1,136 @@ +// +// FLEXUserGlobalEntriesContainer.m +// FLEX +// +// Created by Iulian Onofrei on 2023-02-10. +// Copyright © 2023 FLEX Team. All rights reserved. +// + +#import "FLEXUserGlobalEntriesContainer.h" +#import "FLEXObjectExplorerFactory.h" +#import "FLEXUserGlobalEntriesContainer+Private.h" +#import "FLEXGlobalsViewController.h" + +@interface FLEXUserGlobalEntriesContainer () + +@property (nonatomic, readonly) NSMutableArray *entries; + +@end + +@implementation FLEXUserGlobalEntriesContainer + +- (instancetype)init { + self = [super init]; + if (self) { + _entries = [NSMutableArray new]; + } + return self; +} + +- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock { + [self registerGlobalEntryWithName:entryName + cellAccessoryType:UITableViewCellAccessoryDisclosureIndicator + objectFutureBlock:objectFutureBlock + ]; +} + +- (void)registerGlobalEntryWithName:(NSString *)entryName cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType objectFutureBlock:(id (^)(void))objectFutureBlock { + NSParameterAssert(entryName); + NSParameterAssert(objectFutureBlock); + NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); + + entryName = entryName.copy; + FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{ + return entryName; + } cellAccessoryType:cellAccessoryType viewControllerFuture:^UIViewController *{ + return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()]; + }]; + + [self.entries addObject:entry]; +} + +- (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock { + [self registerGlobalEntryWithName:entryName + cellAccessoryType:UITableViewCellAccessoryDisclosureIndicator + viewControllerFutureBlock:viewControllerFutureBlock + ]; +} + +- (void)registerGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock { + NSParameterAssert(entryName); + NSParameterAssert(viewControllerFutureBlock); + NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); + + entryName = entryName.copy; + FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{ + return entryName; + } cellAccessoryType:cellAccessoryType viewControllerFuture:^UIViewController *{ + UIViewController *viewController = viewControllerFutureBlock(); + NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName); + return viewController; + }]; + + [self.entries addObject:entry]; +} + +- (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction { + [self registerGlobalEntryWithName:entryName + cellAccessoryType:UITableViewCellAccessoryDisclosureIndicator + action:rowSelectedAction + ]; +} + +- (void)registerGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + action:(FLEXGlobalsEntryRowAction)rowSelectedAction { + NSParameterAssert(entryName); + NSParameterAssert(rowSelectedAction); + NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); + + entryName = entryName.copy; + FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString * _Nonnull{ + return entryName; + } cellAccessoryType:cellAccessoryType action:rowSelectedAction]; + + [self.entries addObject:entry]; +} + +- (void)registerNestedGlobalEntryWithName:(NSString *)entryName handler:(FLEXNestedGlobalEntriesHandler)nestedEntriesHandler { + [self registerNestedGlobalEntryWithName:entryName + cellAccessoryType:UITableViewCellAccessoryDisclosureIndicator + handler:nestedEntriesHandler + ]; +} + +- (void)registerNestedGlobalEntryWithName:(NSString *)entryName + cellAccessoryType:(UITableViewCellAccessoryType)cellAccessoryType + handler:(FLEXNestedGlobalEntriesHandler)nestedEntriesHandler { + NSParameterAssert(entryName); + NSParameterAssert(nestedEntriesHandler); + NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); + + entryName = entryName.copy; + FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString * _Nonnull{ + return entryName; + } cellAccessoryType:cellAccessoryType viewControllerFuture:^UIViewController * _Nullable{ + FLEXUserGlobalEntriesContainer *container = [FLEXUserGlobalEntriesContainer new]; + nestedEntriesHandler(container); + + FLEXGlobalsViewController *controller = [FLEXGlobalsViewController new]; + controller.customTitle = entryName; + controller.customEntries = container.entries; + controller.showsDefaultEntries = NO; + + return controller; + }]; + + [self.entries addObject:entry]; +} + +- (void)clearGlobalEntries { + [self.entries removeAllObjects]; +} + +@end diff --git a/Classes/Manager/FLEXManager+Extensibility.h b/Classes/Manager/FLEXManager+Extensibility.h index 83260ed9e8..33e641b87e 100644 --- a/Classes/Manager/FLEXManager+Extensibility.h +++ b/Classes/Manager/FLEXManager+Extensibility.h @@ -8,6 +8,7 @@ #import "FLEXManager.h" #import "FLEXGlobalsEntry.h" +#import "FLEXUserGlobalEntriesContainer.h" NS_ASSUME_NONNULL_BEGIN @@ -15,6 +16,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Globals Screen Entries +/// Use this property to add entries at the top of the list of Global State items. +@property (nonatomic, readonly) FLEXUserGlobalEntriesContainer *globalEntriesContainer; + /// Adds an entry at the top of the list of Global State items. /// Call this method before this view controller is displayed. /// @param entryName The string to be displayed in the cell. @@ -25,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN /// The objectFutureBlock will be invoked from the main thread and may return nil. /// @note The passed block will be copied and retain for the duration of the application, /// you may want to use __weak references. -- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock; +- (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock __attribute((deprecated("Use the same method on the globalEntriesContainer property instead."))); /// Adds an entry at the top of the list of Global State items. /// Call this method before this view controller is displayed. @@ -37,7 +41,7 @@ NS_ASSUME_NONNULL_BEGIN /// @note The passed block will be copied and retain for the duration of the application, /// you may want to use __weak references as needed. - (void)registerGlobalEntryWithName:(NSString *)entryName - viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock; + viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock __attribute((deprecated("Use the same method on the globalEntriesContainer property instead."))); /// Adds an entry at the top of the list of Global State items. /// @param entryName The string to be displayed in the cell. @@ -45,12 +49,23 @@ NS_ASSUME_NONNULL_BEGIN /// with the host table view view controller. Use it to deselect the row or present an alert. /// @note This method must be called from the main thread. /// The rowSelectedAction will be invoked from the main thread. -/// @note The passed block will be copied and retain for the duration of the application, +/// @note The passed block will be copied and retained for the duration of the application, +/// you may want to use __weak references as needed. +- (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction __attribute((deprecated("Use the same method on the globalEntriesContainer property instead."))); + +/// Adds an entry at the top of the list of Global State items. +/// @param entryName The string to be displayed in the cell. +/// @param nestedEntriesHandler When you tap on the row, this block will be invoked +/// with the container object. Use it to register nested entries. +/// @note This method must be called from the main thread. +/// The nestedEntriesHandler will be invoked from the main thread. +/// @note The passed block will be copied and retained for the duration of the application, /// you may want to use __weak references as needed. -- (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction; +- (void)registerNestedGlobalEntryWithName:(NSString *)entryName + handler:(FLEXNestedGlobalEntriesHandler)nestedEntriesHandler __attribute((deprecated("Use the same method on the globalEntriesContainer property instead."))); /// Removes all registered global entries. -- (void)clearGlobalEntries; +- (void)clearGlobalEntries __attribute((deprecated("Use the same method on the globalEntriesContainer property instead."))); #pragma mark - Editing diff --git a/Classes/Manager/FLEXManager+Extensibility.m b/Classes/Manager/FLEXManager+Extensibility.m index 0872ee66ff..24df6b9080 100644 --- a/Classes/Manager/FLEXManager+Extensibility.m +++ b/Classes/Manager/FLEXManager+Extensibility.m @@ -9,7 +9,6 @@ #import "FLEXManager+Extensibility.h" #import "FLEXManager+Private.h" #import "FLEXNavigationController.h" -#import "FLEXObjectExplorerFactory.h" #import "FLEXKeyboardShortcutManager.h" #import "FLEXExplorerViewController.h" #import "FLEXNetworkMITMViewController.h" @@ -17,62 +16,35 @@ #import "FLEXFileBrowserController.h" #import "FLEXArgumentInputStructView.h" #import "FLEXUtility.h" +#import "FLEXUserGlobalEntriesContainer.h" @interface FLEXManager (ExtensibilityPrivate) @property (nonatomic, readonly) UIViewController *topViewController; @end @implementation FLEXManager (Extensibility) +@dynamic globalEntriesContainer; #pragma mark - Globals Screen Entries - (void)registerGlobalEntryWithName:(NSString *)entryName objectFutureBlock:(id (^)(void))objectFutureBlock { - NSParameterAssert(entryName); - NSParameterAssert(objectFutureBlock); - NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); - - entryName = entryName.copy; - FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{ - return entryName; - } viewControllerFuture:^UIViewController *{ - return [FLEXObjectExplorerFactory explorerViewControllerForObject:objectFutureBlock()]; - }]; - - [self.userGlobalEntries addObject:entry]; + [self.globalEntriesContainer registerGlobalEntryWithName:entryName objectFutureBlock:objectFutureBlock]; } - (void)registerGlobalEntryWithName:(NSString *)entryName viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock { - NSParameterAssert(entryName); - NSParameterAssert(viewControllerFutureBlock); - NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); - - entryName = entryName.copy; - FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString *{ - return entryName; - } viewControllerFuture:^UIViewController *{ - UIViewController *viewController = viewControllerFutureBlock(); - NSCAssert(viewController, @"'%@' entry returned nil viewController. viewControllerFutureBlock should never return nil.", entryName); - return viewController; - }]; - - [self.userGlobalEntries addObject:entry]; + [self.globalEntriesContainer registerGlobalEntryWithName:entryName viewControllerFutureBlock:viewControllerFutureBlock]; } - (void)registerGlobalEntryWithName:(NSString *)entryName action:(FLEXGlobalsEntryRowAction)rowSelectedAction { - NSParameterAssert(entryName); - NSParameterAssert(rowSelectedAction); - NSAssert(NSThread.isMainThread, @"This method must be called from the main thread."); - - entryName = entryName.copy; - FLEXGlobalsEntry *entry = [FLEXGlobalsEntry entryWithNameFuture:^NSString * _Nonnull{ - return entryName; - } action:rowSelectedAction]; - - [self.userGlobalEntries addObject:entry]; + [self.globalEntriesContainer registerGlobalEntryWithName:entryName action:rowSelectedAction]; +} + +- (void)registerNestedGlobalEntryWithName:(NSString *)entryName handler:(FLEXNestedGlobalEntriesHandler)nestedEntriesHandler { + [self.globalEntriesContainer registerNestedGlobalEntryWithName:entryName handler:nestedEntriesHandler]; } - (void)clearGlobalEntries { - [self.userGlobalEntries removeAllObjects]; + [self.globalEntriesContainer clearGlobalEntries]; } diff --git a/Classes/Manager/FLEXManager.m b/Classes/Manager/FLEXManager.m index 669a855962..27bc0dd828 100644 --- a/Classes/Manager/FLEXManager.m +++ b/Classes/Manager/FLEXManager.m @@ -13,6 +13,7 @@ #import "FLEXNavigationController.h" #import "FLEXObjectExplorerFactory.h" #import "FLEXFileBrowserController.h" +#import "FLEXUserGlobalEntriesContainer.h" @interface FLEXManager () @@ -21,7 +22,7 @@ @interface FLEXManager () *userGlobalEntries; +@property (nonatomic, readonly) FLEXUserGlobalEntriesContainer *globalEntriesContainer; @property (nonatomic, readonly) NSMutableDictionary *customContentTypeViewers; @end @@ -40,7 +41,7 @@ + (instancetype)sharedManager { - (instancetype)init { self = [super init]; if (self) { - _userGlobalEntries = [NSMutableArray new]; + _globalEntriesContainer = [FLEXUserGlobalEntriesContainer new]; _customContentTypeViewers = [NSMutableDictionary new]; } return self; diff --git a/Classes/Manager/Private/FLEXManager+Private.h b/Classes/Manager/Private/FLEXManager+Private.h index a4f5a913f5..fda60eb4f5 100644 --- a/Classes/Manager/Private/FLEXManager+Private.h +++ b/Classes/Manager/Private/FLEXManager+Private.h @@ -9,15 +9,13 @@ #import "FLEXManager.h" #import "FLEXWindow.h" -@class FLEXGlobalsEntry, FLEXExplorerViewController; +@class FLEXUserGlobalEntriesContainer, FLEXExplorerViewController; @interface FLEXManager (Private) @property (nonatomic, readonly) FLEXWindow *explorerWindow; @property (nonatomic, readonly) FLEXExplorerViewController *explorerViewController; -/// An array of FLEXGlobalsEntry objects that have been registered by the user. -@property (nonatomic, readonly) NSMutableArray *userGlobalEntries; @property (nonatomic, readonly) NSMutableDictionary *customContentTypeViewers; @end diff --git a/Classes/Manager/Private/FLEXUserGlobalEntriesContainer+Private.h b/Classes/Manager/Private/FLEXUserGlobalEntriesContainer+Private.h new file mode 100644 index 0000000000..21a041f739 --- /dev/null +++ b/Classes/Manager/Private/FLEXUserGlobalEntriesContainer+Private.h @@ -0,0 +1,18 @@ +// +// FLEXUserGlobalEntriesContainer+Private.h +// FLEX +// +// Created by Iulian Onofrei on 2023-02-10. +// Copyright © 2023 FLEX Team. All rights reserved. +// + +#import "FLEXUserGlobalEntriesContainer.h" + +@class FLEXGlobalsEntry; + +@interface FLEXUserGlobalEntriesContainer (Private) + +/// An array of FLEXGlobalsEntry objects that have been registered by the user. +@property (nonatomic, readonly) NSMutableArray *entries; + +@end diff --git a/Example/FLEXample/AppDelegate.swift b/Example/FLEXample/AppDelegate.swift index 40378a3a1f..60fa362aa0 100644 --- a/Example/FLEXample/AppDelegate.swift +++ b/Example/FLEXample/AppDelegate.swift @@ -25,8 +25,66 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FLEXManager.shared.isNetworkDebuggingEnabled = true - - // Add at least oen custom user defaults key to explore + + // To show off the global entries, register one of each type + + FLEXManager.shared.globalEntriesContainer.registerGlobalEntry(withName: "Level 1 - Object", cellAccessoryType: .none) { + return "Level 1 - Object" + } + + FLEXManager.shared.globalEntriesContainer.registerGlobalEntry(withName: "Level 1 - View controller", cellAccessoryType: .none) { + let label = UILabel() + label.text = "Level 1 - View controller" + label.translatesAutoresizingMaskIntoConstraints = false + + let controller = UIViewController() + controller.view.backgroundColor = .darkGray + controller.view.addSubview(label) + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: controller.view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: controller.view.centerYAnchor) + ]) + + return controller + } + + FLEXManager.shared.globalEntriesContainer.registerGlobalEntry(withName: "Level 1 - Action", cellAccessoryType: .none) { host in + FLEXAlert.showQuickAlert("Level 1 - Action", from: host) + } + + FLEXManager.shared.globalEntriesContainer.registerNestedGlobalEntry(withName: "Level 1 - Nested") { container in + container.registerGlobalEntry(withName: "Level 2 - Object", cellAccessoryType: .none) { + return "Level 2 - Object" + } + + container.registerGlobalEntry(withName: "Level 2 - View controller", cellAccessoryType: .none) { + let label = UILabel() + label.text = "Level 2 - View controller" + label.translatesAutoresizingMaskIntoConstraints = false + + let controller = UIViewController() + controller.view.backgroundColor = .darkGray + controller.view.addSubview(label) + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: controller.view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: controller.view.centerYAnchor) + ]) + + return controller + } + + container.registerGlobalEntry(withName: "Level 2 - Action", cellAccessoryType: .none) { host in + FLEXAlert.showQuickAlert("Level 2 - Action", from: host) + } + + container.registerNestedGlobalEntry(withName: "Level 2 - Nested") { level2Container in + level2Container.registerGlobalEntry(withName: "Level 3 - Action", cellAccessoryType: .none) { host in + FLEXAlert.showQuickAlert("Level 3 - Action", from: host) + } + } + } + + // Add at least one custom user defaults key to explore UserDefaults.standard.set("foo", forKey: "FLEXamplePrefFoo") // To show off the system log viewer, send 10 example log messages at 3 second intervals diff --git a/FLEX.podspec b/FLEX.podspec index e4f520c9a3..9010d2f395 100644 --- a/FLEX.podspec +++ b/FLEX.podspec @@ -42,18 +42,19 @@ Pod::Spec.new do |spec| spec.compiler_flags = "-Wno-unsupported-availability-guard", "-Wno-deprecated-declarations" spec.public_header_files = [ "Classes/*.h", "Classes/Manager/*.h", "Classes/Toolbar/*.h", "Classes/Core/Controllers/*.h", "Classes/Core/Views/*.h", - "Classes/Core/Views/Cells/*.h", "Classes/Core/*.h", + "Classes/Core/Views/Cells/*.h", "Classes/Core/*.h", "Classes/Utility/Categories/*.h", "Classes/Utility/Runtime/Objc/**/*.h", "Classes/ObjectExplorers/*.h", "Classes/ObjectExplorers/Sections/*.h", - + "Classes/Utility/FLEXMacros.h", "Classes/Utility/FLEXAlert.h", "Classes/Utility/FLEXResources.h", "Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.h", "Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.h", "Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h", - "Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserController.h" + "Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserController.h", + "Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.h" ] end diff --git a/generate-spm-headers.sh b/generate-spm-headers.sh index 422a35cda3..2a0c15bb9f 100755 --- a/generate-spm-headers.sh +++ b/generate-spm-headers.sh @@ -29,6 +29,7 @@ generate_headers() { # # "Classes/*.h", "Classes/Manager/*.h", "Classes/Toolbar/*.h", # "Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h", +# "Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.h", # "Classes/Core/**/*.h", "Classes/Utility/Runtime/Objc/**/*.h", # "Classes/ObjectExplorers/**/*.h", "Classes/Editing/**/*.h", # "Classes/Utility/FLEXMacros.h", "Classes/Utility/Categories/*.h", @@ -63,6 +64,7 @@ makeheader "Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.h" makeheader "Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcutsSection.h" makeheader "Classes/GlobalStateExplorers/Globals/FLEXGlobalsEntry.h" makeheader "Classes/GlobalStateExplorers/FileBrowser/FLEXFileBrowserController.h" +makeheader "Classes/GlobalStateExplorers/Globals/FLEXUserGlobalEntriesContainer.h" # Print all folders in Classes for use in Package.swift for folder in `find "Classes" -type d`; do