Compare commits

...

6 Commits

111 changed files with 5418 additions and 165 deletions
+5
View File
@@ -20,3 +20,8 @@ DerivedData
/Example/Pods
Podfile.lock
IDEWorkspaceChecks.plist
#tvOS specific
libflex_*
flexinjected/packages/*
layout/Library/Frameworks/*.framework
@@ -188,7 +188,7 @@
}
#if FLEX_AT_LEAST_IOS13_SDK
#if !TARGET_OS_TV
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
FLEXTableViewSection *section = self.filterDelegate.sections[indexPath.section];
NSString *title = [section menuTitleForRow:indexPath.row];
@@ -206,7 +206,7 @@
return nil;
}
#endif
#endif
@end
@@ -9,6 +9,8 @@
#import "FLEXNavigationController.h"
#import "FLEXExplorerViewController.h"
#import "FLEXTabList.h"
#import "FLEXColor.h"
#import "NSObject+FLEX_Reflection.h"
@interface UINavigationController (Private) <UIGestureRecognizerDelegate>
- (void)_gestureRecognizedInteractiveHide:(UIGestureRecognizer *)sender;
@@ -48,8 +50,10 @@
if (@available(iOS 13, *)) {
switch (self.modalPresentationStyle) {
case UIModalPresentationAutomatic:
#if !TARGET_OS_TV
case UIModalPresentationPageSheet:
case UIModalPresentationFormSheet:
#endif
break;
default:
@@ -71,6 +75,14 @@
self.didSetupPendingDismissButtons = YES;
}
#if TARGET_OS_TV
if ([self darkMode]){
self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
} else {
self.view.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8];
}
#endif
}
- (void)viewDidAppear:(BOOL)animated {
@@ -85,6 +97,8 @@
self.waitingToAddTab = NO;
}
}
//the timing is janky here but its better than nothing for now.
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
@@ -132,6 +146,7 @@
}
- (void)addNavigationBarSwipeGesture {
#if !TARGET_OS_TV
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(handleNavigationBarSwipe:)
];
@@ -139,6 +154,7 @@
swipe.delegate = self;
self.navigationBarSwipeGesture = swipe;
[self.navigationBar addGestureRecognizer:swipe];
#endif
}
- (void)handleNavigationBarSwipe:(UISwipeGestureRecognizer *)sender {
@@ -149,12 +165,14 @@
- (void)handleNavigationBarTap:(UIGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
#if !TARGET_OS_TV
if (self.toolbarHidden) {
[self setToolbarHidden:NO animated:YES];
}
#endif
}
}
#if !TARGET_OS_TV
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)g2 {
if (g1 == self.navigationBarSwipeGesture && g2 == self.barHideOnSwipeGestureRecognizer) {
return YES;
@@ -162,8 +180,9 @@
return NO;
}
#endif
- (void)_gestureRecognizedInteractiveHide:(UIPanGestureRecognizer *)sender {
#if !TARGET_OS_TV
if (sender.state == UIGestureRecognizerStateRecognized) {
BOOL show = self.topViewController.toolbarItems.count;
CGFloat yTranslation = [sender translationInView:self.view].y;
@@ -176,6 +195,7 @@
[self setToolbarHidden:YES animated:YES];
}
}
#endif
}
@end
@@ -16,6 +16,7 @@
#import "FLEXResources.h"
#import "UIBarButtonItem+FLEX.h"
#import <objc/runtime.h>
#import "fakes.h"
@interface Block : NSObject
- (void)invoke;
@@ -39,6 +40,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
@property (nonatomic) UIBarButtonItem *middleToolbarItem;
@property (nonatomic) UIBarButtonItem *middleLeftToolbarItem;
@property (nonatomic) UIBarButtonItem *leftmostToolbarItem;
@end
@implementation FLEXTableViewController
@@ -52,7 +54,11 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
- (id)init {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
#if !TARGET_OS_TV
self = [self initWithStyle:UITableViewStyleInsetGrouped];
#else
self = [self initWithStyle:UITableViewStyleGrouped];
#endif
} else {
self = [self initWithStyle:UITableViewStyleGrouped];
}
@@ -89,6 +95,15 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
return (id)self.view.window;
}
/**
search is handled a bit differently on tvOS and i couldnt get its pardigm to cooperate, thankfully the UISearchController never needs to be visible to actually work its magic.
since 3D snapshot viewing doesn't exist on tvOS the leftBarButtonItem is the perfect spot to add a 'search' button. this search button will present a new text entry controller
the alpha on this viewController is decreased to 0.6 to make it possible to view the filtering changes underneath in realtime. The zero rect textfield associated with
the search button acts as a proxy to transfer the text to our search bar as necessary
*/
- (void)setShowsSearchBar:(BOOL)showsSearchBar {
if (_showsSearchBar == showsSearchBar) return;
_showsSearchBar = showsSearchBar;
@@ -99,11 +114,13 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.searchController.searchBar.placeholder = @"Filter";
self.searchController.searchResultsUpdater = (id)self;
self.searchController.delegate = (id)self;
#if !TARGET_OS_TV
self.searchController.searchBar.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
#endif
self.searchController.hidesNavigationBarDuringPresentation = NO;
/// Not necessary in iOS 13; remove this when iOS 13 is the minimum deployment target
self.searchController.searchBar.delegate = self;
self.automaticallyShowsSearchBarCancelButton = YES;
#if FLEX_AT_LEAST_IOS13_SDK
@@ -111,7 +128,6 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.searchController.automaticallyShowsScopeBar = NO;
}
#endif
[self addSearchController:self.searchController];
} else {
// Search already shown and just set to NO, so remove it
@@ -209,9 +225,11 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}
- (void)disableToolbar {
#if !TARGET_OS_TV
self.navigationController.toolbarHidden = YES;
self.navigationController.hidesBarsOnSwipe = NO;
self.toolbarItems = nil;
#endif
}
@@ -241,9 +259,10 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
// Toolbar
#if !TARGET_OS_TV
self.navigationController.toolbarHidden = NO;
self.navigationController.hidesBarsOnSwipe = YES;
#endif
// On iOS 13, the root view controller shows it's search bar no matter what.
// Turning this off avoids some weird flash the navigation bar does when we
// toggle navigationItem.hidesSearchBarWhenScrolling on and off. The flash
@@ -262,10 +281,16 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
// When going back, make the search bar reappear instead of hiding
if (@available(iOS 11.0, *)) {
if ((self.pinSearchBar || self.showSearchBarInitially) && !self.didInitiallyRevealSearchBar) {
#if !TARGET_OS_TV
self.navigationItem.hidesSearchBarWhenScrolling = NO;
#endif
}
}
#if TARGET_OS_TV
UIEdgeInsets insets = self.tableView.contentInset;
insets.top+=20;
self.tableView.contentInset = insets;
#endif
[self setupToolbarItems];
}
@@ -280,7 +305,9 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
// the search bar appear initially results in a bugged search bar that
// becomes transparent and floats over the screen as you scroll
[UIView animateWithDuration:0.2 animations:^{
#if !TARGET_OS_TV
self.navigationItem.hidesSearchBarWhenScrolling = YES;
#endif
[self.navigationController.view setNeedsLayout];
[self.navigationController.view layoutIfNeeded];
}];
@@ -313,7 +340,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
if (!self.isViewLoaded) {
return;
}
#if !TARGET_OS_TV
self.toolbarItems = @[
self.leftmostToolbarItem,
UIBarButtonItem.flex_flexibleSpace,
@@ -331,7 +358,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
// This does not work for anything but fixed spaces for some reason
// item.width = 60;
}
#endif
// Disable tabs entirely when not presented by FLEXExplorerViewController
UIViewController *presenter = self.navigationController.presentingViewController;
if (![presenter isKindOfClass:[FLEXExplorerViewController class]]) {
@@ -459,6 +486,12 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}
- (void)addSearchController:(UISearchController *)controller {
#if TARGET_OS_TV
KBSearchButton *sb = [KBSearchButton buttonWithType:UIButtonTypeSystem];
sb.searchBar = self.searchController.searchBar;
UIBarButtonItem *searchButton = [[UIBarButtonItem alloc] initWithCustomView:sb];
self.navigationItem.leftBarButtonItem = searchButton;
#else
if (@available(iOS 11.0, *)) {
self.navigationItem.searchController = controller;
} else {
@@ -472,17 +505,21 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
// Move the carousel down if it's already there
if (self.showsCarousel) {
self.carousel.frame = FLEXRectSetY(
self.carousel.frame, subviewFrame.size.height
);
self.carousel.frame, subviewFrame.size.height
);
frame.size.height += self.carousel.frame.size.height;
}
self.tableHeaderViewContainer.frame = frame;
[self layoutTableHeaderIfNeeded];
}
#endif
}
- (void)removeSearchController:(UISearchController *)controller {
#if TARGET_OS_TV
self.navigationItem.leftBarButtonItem = nil;
#else
if (@available(iOS 11.0, *)) {
self.navigationItem.searchController = nil;
} else {
@@ -497,6 +534,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
_tableHeaderViewContainer = nil;
}
}
#endif
}
- (UIView *)tableHeaderViewContainer {
@@ -548,7 +586,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
}
}
#if !TARGET_OS_TV
#pragma mark UISearchControllerDelegate
- (void)willPresentSearchController:(UISearchController *)searchController {
@@ -572,19 +610,51 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
[self updateSearchResultsForSearchController:self.searchController];
}
#endif
#pragma mark Table View
/// Not having a title in the first section looks weird with a rounded-corner table view style
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (@available(iOS 13, *)) {
#if !TARGET_OS_TV
if (self.style == UITableViewStyleInsetGrouped) {
return @" ";
}
#endif
}
return nil; // For plain/gropued style
}
#if TARGET_OS_TV
#pragma mark tvOS
/*
This tracks our most recently focused cell so when we leave / return to this view we can refocus to the proper index path.
*/
- (void)tableView:(UITableView *)tableView didUpdateFocusInContext:(UITableViewFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
[coordinator addCoordinatedAnimations:^{
NSIndexPath *nextIndexPath = context.nextFocusedIndexPath;
KBTableView *table = (KBTableView *)tableView;
if ([table respondsToSelector:@selector(setSelectedIndexPath:)]){
if (nextIndexPath != nil){
[table setSelectedIndexPath:nextIndexPath];
}
}
} completion:nil];
}
- (NSArray *)preferredFocusEnvironments {
if (self.tableView.selectedIndexPath){
return @[[self.tableView cellForRowAtIndexPath:self.tableView.selectedIndexPath]];
}
return @[self];
}
#endif
@end
+2
View File
@@ -101,7 +101,9 @@
image:copyIcon
identifier:nil
handler:^(__kindof UIAction *action) {
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = value;
#endif
}
];
if (!value.length) {
+2 -1
View File
@@ -37,8 +37,9 @@
UIFont *cellFont = UIFont.flex_defaultTableCellFont;
self.titleLabel.font = cellFont;
self.subtitleLabel.font = cellFont;
#if !TARGET_OS_TV
self.subtitleLabel.textColor = FLEXColor.deemphasizedTextColor;
#endif
self.titleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
self.subtitleLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+5
View File
@@ -35,6 +35,11 @@ extern FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell;
+ (instancetype)plainTableView;
+ (instancetype)style:(UITableViewStyle)style;
#if TARGET_OS_TV
/// tvOS tries to keep your selected index remembered when pushing and popping between views in a navigation controller, it doesn't do a very good job, this is to keep track of it ourselves.
@property (nonatomic, strong) NSIndexPath *selectedIndexPath;
#endif
/// You do not need to register classes for any of the default reuse identifiers above
/// (annotated as \c FLEXTableViewCellReuseIdentifier types) unless you wish to provide
/// a custom cell for any of those reuse identifiers. By default, \c FLEXTableViewCell,
+8
View File
@@ -32,7 +32,11 @@ FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell";
+ (instancetype)flexDefaultTableView {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
#if !TARGET_OS_TV
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
#else
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
#endif
} else {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
@@ -46,7 +50,11 @@ FLEXTableViewCellReuseIdentifier const kFLEXCodeFontCell = @"kFLEXCodeFontCell";
+ (id)groupedTableView {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
#if !TARGET_OS_TV
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleInsetGrouped];
#else
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
#endif
} else {
return [[self alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
}
@@ -9,12 +9,16 @@
#import "FLEXArgumentInputColorView.h"
#import "FLEXUtility.h"
#import "FLEXRuntimeUtility.h"
#import "fakes.h"
@protocol FLEXColorComponentInputViewDelegate;
@interface FLEXColorComponentInputView : UIView
#if !TARGET_OS_TV
@property (nonatomic) UISlider *slider;
#else
@property (nonatomic) KBSlider *slider;
#endif
@property (nonatomic) UILabel *valueLabel;
@property (nonatomic, weak) id <FLEXColorComponentInputViewDelegate> delegate;
@@ -33,7 +37,11 @@
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
#if !TARGET_OS_TV
self.slider = [UISlider new];
#else
self.slider = [[KBSlider alloc] initWithFrame:CGRectMake(0, 0, 1000, 53)];
#endif
[self.slider addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged];
[self addSubview:self.slider];
@@ -58,13 +66,17 @@
[super layoutSubviews];
const CGFloat kValueLabelWidth = 50.0;
UIEdgeInsets sliderInset = UIEdgeInsetsZero;
#if TARGET_OS_TV
sliderInset.left = 40;
sliderInset.right = 40;
#endif
[self.slider sizeToFit];
CGFloat sliderWidth = self.bounds.size.width - kValueLabelWidth;
self.slider.frame = CGRectMake(0, 0, sliderWidth, self.slider.frame.size.height);
CGFloat sliderWidth = self.bounds.size.width - kValueLabelWidth - sliderInset.left - sliderInset.right;
self.slider.frame = CGRectMake(sliderInset.left, 0, sliderWidth, self.slider.frame.size.height);
[self.valueLabel sizeToFit];
CGFloat valueLabelOriginX = CGRectGetMaxX(self.slider.frame);
CGFloat valueLabelOriginX = CGRectGetMaxX(self.slider.frame) + sliderInset.right/2.0;
CGFloat valueLabelOriginY = FLEXFloor((self.slider.frame.size.height - self.valueLabel.frame.size.height) / 2.0);
self.valueLabel.frame = CGRectMake(valueLabelOriginX, valueLabelOriginY, kValueLabelWidth, self.valueLabel.frame.size.height);
}
@@ -8,11 +8,16 @@
#import "FLEXArgumentInputDateView.h"
#import "FLEXRuntimeUtility.h"
#import "fakes.h"
#import <TargetConditionals.h>
@interface FLEXArgumentInputDateView ()
#if TARGET_OS_TV
@property (nonatomic) KBDatePickerView *datePicker;
#else
@property (nonatomic) UIDatePicker *datePicker;
#endif
@end
@implementation FLEXArgumentInputDateView
@@ -20,11 +25,17 @@
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
#if !TARGET_OS_TV
self.datePicker = [UIDatePicker new];
self.datePicker.datePickerMode = UIDatePickerModeDateAndTime;
// Using UTC, because that's what the NSDate description prints
self.datePicker.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
self.datePicker.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
#else
self.datePicker = [[KBDatePickerView alloc] init];
#endif
[self addSubview:self.datePicker];
}
return self;
@@ -10,6 +10,7 @@
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXArgumentInputFontsPickerView.h"
#import <TargetConditionals.h>
@interface FLEXArgumentInputFontView ()
@@ -74,11 +75,15 @@
CGFloat runningOriginY = self.topInputFieldVerticalLayoutGuide;
CGSize fontNameFitSize = [self.fontNameInput sizeThatFits:self.bounds.size];
self.fontNameInput.frame = CGRectMake(0, runningOriginY, fontNameFitSize.width, fontNameFitSize.height);
CGFloat padding = 0;
#if TARGET_OS_TV
padding = 40;
#endif
self.fontNameInput.frame = CGRectMake(0, runningOriginY, fontNameFitSize.width, fontNameFitSize.height + padding);
runningOriginY = CGRectGetMaxY(self.fontNameInput.frame) + [[self class] verticalPaddingBetweenFields];
CGSize pointSizeFitSize = [self.pointSizeInput sizeThatFits:self.bounds.size];
self.pointSizeInput.frame = CGRectMake(0, runningOriginY, pointSizeFitSize.width, pointSizeFitSize.height);
self.pointSizeInput.frame = CGRectMake(0, runningOriginY, pointSizeFitSize.width, pointSizeFitSize.height + padding);
}
+ (CGFloat)verticalPaddingBetweenFields {
@@ -8,5 +8,9 @@
#import "FLEXArgumentInputTextView.h"
#if TARGET_OS_TV
@interface FLEXArgumentInputFontsPickerView : FLEXArgumentInputTextView
#else
@interface FLEXArgumentInputFontsPickerView : FLEXArgumentInputTextView <UIPickerViewDataSource, UIPickerViewDelegate>
#endif
@end
@@ -8,7 +8,8 @@
#import "FLEXArgumentInputFontsPickerView.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXFontListTableViewController.h"
#import "NSObject+FLEX_Reflection.h"
@interface FLEXArgumentInputFontsPickerView ()
@property (nonatomic) NSMutableArray<NSString *> *availableFonts;
@@ -23,7 +24,17 @@
if (self) {
self.targetSize = FLEXArgumentInputViewSizeSmall;
[self createAvailableFonts];
#if TARGET_OS_TV
FLEXFontListTableViewController *fontListController = [FLEXFontListTableViewController new];
fontListController.itemSelectedBlock = ^(NSString *fontName) {
[self.inputTextView setText:fontName];
[[self topViewController] dismissViewControllerAnimated:true completion:nil];
};
self.inputTextView.inputViewController = fontListController;
#else
self.inputTextView.inputView = [self createFontsPicker];
#endif
}
return self;
}
@@ -33,7 +44,9 @@
if ([self.availableFonts indexOfObject:inputValue] == NSNotFound) {
[self.availableFonts insertObject:inputValue atIndex:0];
}
#if !TARGET_OS_TV
[(UIPickerView *)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
#endif
}
- (id)inputValue {
@@ -42,14 +55,6 @@
#pragma mark - private
- (UIPickerView*)createFontsPicker {
UIPickerView *fontsPicker = [UIPickerView new];
fontsPicker.dataSource = self;
fontsPicker.delegate = self;
fontsPicker.showsSelectionIndicator = YES;
return fontsPicker;
}
- (void)createAvailableFonts {
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray new];
for (NSString *eachFontFamily in UIFont.familyNames) {
@@ -60,6 +65,16 @@
self.availableFonts = [NSMutableArray arrayWithArray:[unsortedFontsArray sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]];
}
#if !TARGET_OS_TV
- (UIPickerView*)createFontsPicker {
UIPickerView *fontsPicker = [UIPickerView new];
fontsPicker.dataSource = self;
fontsPicker.delegate = self;
fontsPicker.showsSelectionIndicator = YES;
return fontsPicker;
}
#pragma mark - UIPickerViewDataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
@@ -93,4 +108,6 @@
self.inputTextView.text = self.availableFonts[row];
}
#endif
@end
@@ -7,11 +7,13 @@
//
#import "FLEXArgumentInputSwitchView.h"
#import "fakes.h"
@interface FLEXArgumentInputSwitchView ()
#if !TARGET_OS_TV
@property (nonatomic) UISwitch *inputSwitch;
#else
@property (nonatomic) UIFakeSwitch *inputSwitch;
#endif
@end
@implementation FLEXArgumentInputSwitchView
@@ -19,14 +21,24 @@
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
#if !TARGET_OS_TV
self.inputSwitch = [UISwitch new];
[self.inputSwitch addTarget:self action:@selector(switchValueDidChange:) forControlEvents:UIControlEventValueChanged];
[self.inputSwitch sizeToFit];
[self.inputSwitch addTarget:self action:@selector(switchValueDidChange:) forControlEvents:UIControlEventValueChanged];
#else
self.inputSwitch = [UIFakeSwitch newSwitch];
[self.inputSwitch addTarget:self action:@selector(changeSwitchValue:) forControlEvents:UIControlEventPrimaryActionTriggered];
#endif
[self addSubview:self.inputSwitch];
}
return self;
}
- (void)changeSwitchValue:(UIFakeSwitch *)switchView {
[switchView setOn:!switchView.isOn];
[self switchValueDidChange:switchView];
}
#pragma mark Input/Output
@@ -59,13 +71,20 @@
- (void)layoutSubviews {
[super layoutSubviews];
#if TARGET_OS_TV
self.inputSwitch.frame = CGRectMake(50, 50, 200, 70);
#else
self.inputSwitch.frame = CGRectMake(0, self.topInputFieldVerticalLayoutGuide, self.inputSwitch.frame.size.width, self.inputSwitch.frame.size.height);
#endif
}
- (CGSize)sizeThatFits:(CGSize)size {
CGSize fitSize = [super sizeThatFits:size];
#if TARGET_OS_TV
fitSize.height += 110;
#else
fitSize.height += self.inputSwitch.frame.size.height;
#endif
return fitSize;
}
@@ -7,12 +7,18 @@
//
#import "FLEXArgumentInputView.h"
#if TARGET_OS_TV
#import "KBSelectableTextView.h"
#endif
@interface FLEXArgumentInputTextView : FLEXArgumentInputView <UITextViewDelegate>
// For subclass eyes only
#if TARGET_OS_TV
@property (nonatomic, readonly) KBSelectableTextView *inputTextView;
#else
@property (nonatomic, readonly) UITextView *inputTextView;
#endif
@property (nonatomic) NSString *inputPlaceholderText;
@end
@@ -12,7 +12,11 @@
@interface FLEXArgumentInputTextView ()
#if TARGET_OS_TV
@property (nonatomic) KBSelectableTextView *inputTextView;
#else
@property (nonatomic) UITextView *inputTextView;
#endif
@property (nonatomic) UILabel *placeholderLabel;
@property (nonatomic, readonly) NSUInteger numberOfInputLines;
@@ -23,7 +27,12 @@
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
#if TARGET_OS_TV
self.inputTextView = [[KBSelectableTextView alloc] initWithFrame:CGRectZero];
#else
self.inputTextView = [UITextView new];
self.inputTextView.inputAccessoryView = [self createToolBar];
#endif
self.inputTextView.font = [[self class] inputFont];
self.inputTextView.backgroundColor = FLEXColor.secondaryGroupedBackgroundColor;
self.inputTextView.layer.cornerRadius = 10.f;
@@ -31,7 +40,6 @@
self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.inputTextView.autocorrectionType = UITextAutocorrectionTypeNo;
self.inputTextView.delegate = self;
self.inputTextView.inputAccessoryView = [self createToolBar];
if (@available(iOS 11, *)) {
[self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"];
} else {
@@ -52,7 +60,7 @@
}
#pragma mark - Private
#if !TARGET_OS_TV
- (UIToolbar *)createToolBar {
UIToolbar *toolBar = [UIToolbar new];
[toolBar sizeToFit];
@@ -71,7 +79,7 @@
toolBar.items = @[spaceItem, pasteItem, doneItem];
return toolBar;
}
#endif
- (void)setInputPlaceholderText:(NSString *)placeholder {
self.placeholderLabel.text = placeholder;
if (placeholder.length) {
@@ -127,7 +135,11 @@
}
- (CGFloat)inputTextViewHeight {
return ceil([[self class] inputFont].lineHeight * self.numberOfInputLines) + 16.0;
CGFloat padding = 16.0;
#if TARGET_OS_TV
padding = 40.0;
#endif
return ceil([[self class] inputFont].lineHeight * self.numberOfInputLines) + padding;
}
- (CGSize)sizeThatFits:(CGSize)size {
@@ -18,10 +18,13 @@ NS_ASSUME_NONNULL_BEGIN
+ (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit;
/// @return nil if the ivar type is unsupported
+ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit;
#if TARGET_OS_TV
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIButton *getterButton;
#else
/// Subclasses can change the button title via the \c title property
@property (nonatomic, readonly) UIBarButtonItem *getterButton;
#endif
- (void)getterButtonPressed:(id)sender;
@end
@@ -14,6 +14,8 @@
#import "FLEXUtility.h"
#import "FLEXColor.h"
#import "UIBarButtonItem+FLEX.h"
#import <TargetConditionals.h>
#import "FLEXArgumentInputDateView.h"
@interface FLEXFieldEditorViewController () <FLEXArgumentInputViewDelegate>
@@ -55,7 +57,7 @@
[super viewDidLoad];
self.view.backgroundColor = FLEXColor.groupedBackgroundColor;
#if !TARGET_OS_TV
// Create getter button
_getterButton = [[UIBarButtonItem alloc]
initWithTitle:@"Get"
@@ -63,9 +65,23 @@
target:self
action:@selector(getterButtonPressed:)
];
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.actionButton
];
#else
_getterButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_getterButton setTitle:@"Get" forState:UIControlStateNormal];
[_getterButton addTarget:self action:@selector(getterButtonPressed:) forControlEvents:UIControlEventPrimaryActionTriggered];
_getterButton.frame = CGRectMake(100, 600, 200, 70);
[self.view addSubview:_getterButton];
UIFocusGuide *focusGuide = [[UIFocusGuide alloc] init];
[self.view addLayoutGuide:focusGuide];
[focusGuide.topAnchor constraintEqualToAnchor:self.actionButton.topAnchor].active = true;
[focusGuide.bottomAnchor constraintEqualToAnchor:self.getterButton.bottomAnchor].active = true;
focusGuide.preferredFocusEnvironments = self.preferredFocusEnvironments;
#endif
// Configure input view
self.fieldEditorView.fieldDescription = self.fieldDescription;
@@ -73,18 +89,32 @@
inputView.inputValue = self.currentValue;
inputView.delegate = self;
self.fieldEditorView.argumentInputViews = @[inputView];
// Don't show a "set" button for switches; we mutate when the switch is flipped
if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
self.actionButton.enabled = NO;
#if !TARGET_OS_TV
self.actionButton.title = @"Flip the switch to call the setter";
// Put getter button before setter button
// Put getter button before setter button
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace, self.actionButton, self.getterButton
];
#endif
}
}
- (NSArray *)preferredFocusEnvironments {
if ([self actionButton] && _getterButton){
return @[[self actionButton],_getterButton];
} else {
if ([self actionButton]){
return @[[self actionButton]];
} else if (_getterButton){
return @[_getterButton];
}
}
return nil;
}
- (void)actionButtonPressed:(id)sender {
if (self.property) {
id userInputObject = self.firstInputView.inputValue;
@@ -125,6 +155,27 @@
}
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
#if TARGET_OS_TV
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:self.typeEncoding];
if ([inputView isKindOfClass:[FLEXArgumentInputDateView class]]){
[self actionButton].frame = CGRectMake(100, 350, 200, 60);
[self getterButton].frame = CGRectMake(100, 550, 200, 60);
return;
}
CGRect getterFrame = _getterButton.frame;
CGFloat actionOffset = [[self actionButton] frame].origin.y;
//CGRect fieldEditorFrame = self.fieldEditorView.frame;
//CGFloat buttonOffset = (fieldEditorFrame.origin.y + fieldEditorFrame.size.height) + (130 + 67);
getterFrame.origin.y = actionOffset;
_getterButton.frame = getterFrame;
#endif
}
#pragma mark - Private
- (id)currentValue {
@@ -39,9 +39,11 @@
- (void)viewDidLoad {
[super viewDidLoad];
#if !TARGET_OS_TV
self.actionButton.title = @"Call";
#else
[self.actionButton setTitle:@"Call" forState:UIControlStateNormal];
#endif
// Configure field editor view
self.fieldEditorView.argumentInputViews = [self argumentInputViews];
self.fieldEditorView.fieldDescription = [NSString stringWithFormat:
@@ -39,8 +39,13 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, nullable) FLEXArgumentInputView *firstInputView;
@property (nonatomic, readonly) FLEXFieldEditorView *fieldEditorView;
#if TARGET_OS_TV
/// Subclasses can change the button title via the button's \c title property
@property (nonatomic, readonly) UIButton *actionButton;
#else
/// Subclasses can change the button title via the button's \c title property
@property (nonatomic, readonly) UIBarButtonItem *actionButton;
#endif
/// Subclasses should override to provide "set" functionality.
/// The commit handler--if present--is called here.
@@ -16,6 +16,7 @@
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXObjectExplorerViewController.h"
#import "UIBarButtonItem+FLEX.h"
#import "FLEXArgumentInputDateView.h"
@interface FLEXVariableEditorViewController () <UIScrollViewDelegate>
@property (nonatomic) UIScrollView *scrollView;
@@ -35,7 +36,8 @@
_target = target;
_data = data;
_commitHandler = onCommit;
[NSNotificationCenter.defaultCenter
#if !TARGET_OS_TV
[NSNotificationCenter.defaultCenter
addObserver:self selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification object:nil
];
@@ -43,6 +45,7 @@
addObserver:self selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil
];
#endif
}
return self;
@@ -55,6 +58,7 @@
#pragma mark - UIViewController methods
- (void)keyboardDidShow:(NSNotification *)notification {
#if !TARGET_OS_TV
CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size;
UIEdgeInsets scrollInsets = self.scrollView.contentInset;
@@ -70,9 +74,11 @@
break;
}
}
#endif
}
- (void)keyboardWillHide:(NSNotification *)notification {
UIEdgeInsets scrollInsets = self.scrollView.contentInset;
scrollInsets.bottom = 0.0;
self.scrollView.contentInset = scrollInsets;
@@ -93,7 +99,7 @@
_fieldEditorView = [FLEXFieldEditorView new];
self.fieldEditorView.targetDescription = [NSString stringWithFormat:@"%@ %p", [self.target class], self.target];
[self.scrollView addSubview:self.fieldEditorView];
#if !TARGET_OS_TV
_actionButton = [[UIBarButtonItem alloc]
initWithTitle:@"Set"
style:UIBarButtonItemStyleDone
@@ -103,12 +109,30 @@
self.navigationController.toolbarHidden = NO;
self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, self.actionButton];
#else
_actionButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_actionButton setTitle:@"Set" forState:UIControlStateNormal];
[_actionButton addTarget:self action:@selector(actionButtonPressed:) forControlEvents:UIControlEventPrimaryActionTriggered];
_actionButton.frame = CGRectMake(500, 600, 200, 70);
[self.view addSubview:_actionButton];
#endif
}
- (void)viewWillLayoutSubviews {
CGSize constrainSize = CGSizeMake(self.scrollView.bounds.size.width, CGFLOAT_MAX);
CGSize fieldEditorSize = [self.fieldEditorView sizeThatFits:constrainSize];
self.fieldEditorView.frame = CGRectMake(0, 0, fieldEditorSize.width, fieldEditorSize.height);
#if TARGET_OS_TV
CGRect actionFrame = _actionButton.frame;
CGRect fieldEditorFrame = self.fieldEditorView.frame;
CGFloat buttonOffset = (fieldEditorFrame.origin.y + fieldEditorFrame.size.height) + (130 + 67);
actionFrame.origin.y = buttonOffset;
_actionButton.frame = actionFrame;
#endif
self.scrollView.contentSize = fieldEditorSize;
}
@@ -32,8 +32,9 @@
- (void)viewDidLoad {
[super viewDidLoad];
#if !TARGET_OS_TV
self.navigationController.hidesBarsOnSwipe = NO;
#endif
self.tableView.allowsMultipleSelectionDuringEditing = YES;
[self reloadData];
@@ -56,6 +57,7 @@
- (void)setupDefaultBarItems {
self.navigationItem.rightBarButtonItem = FLEXBarButtonItemSystem(Done, self, @selector(dismissAnimated));
#if !TARGET_OS_TV
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
FLEXBarButtonItemSystem(Edit, self, @selector(toggleEditing)),
@@ -63,18 +65,20 @@
// Disable editing if no bookmarks available
self.toolbarItems.lastObject.enabled = self.bookmarks.count > 0;
#endif
}
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
#if !TARGET_OS_TV
self.toolbarItems = @[
[UIBarButtonItem flex_itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
// We use a non-system done item because we change its title dynamically
[UIBarButtonItem flex_doneStyleitemWithTitle:@"Done" target:self action:@selector(toggleEditing)]
];
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
#endif
}
- (FLEXExplorerViewController *)corePresenter {
@@ -198,8 +202,10 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.editing) {
// Case: editing with multi-select
#if !TARGET_OS_TV
self.toolbarItems.lastObject.title = @"Remove Selected";
self.toolbarItems.lastObject.tintColor = FLEXColor.destructiveColor;
#endif
} else {
// Case: selected a bookmark
[self dismissAnimated:self.bookmarks[indexPath.row]];
@@ -210,8 +216,10 @@
NSParameterAssert(self.editing);
if (tableView.indexPathsForSelectedRows.count == 0) {
#if !TARGET_OS_TV
self.toolbarItems.lastObject.title = @"Done";
self.toolbarItems.lastObject.tintColor = self.view.tintColor;
#endif
}
}
@@ -21,6 +21,8 @@
#import "FLEXWindowManagerController.h"
#import "FLEXViewControllersViewController.h"
#import "NSUserDefaults+FLEX.h"
#import "FLEXManager.h"
#import "FLEXResources.h"
typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
FLEXExplorerModeDefault,
@@ -28,7 +30,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
FLEXExplorerModeMove
};
@interface FLEXExplorerViewController () <FLEXHierarchyDelegate, UIAdaptivePresentationControllerDelegate>
@interface FLEXExplorerViewController () <FLEXHierarchyDelegate, UIAdaptivePresentationControllerDelegate>{
UIImageView *cursorView;
}
/// Tracks the currently active tool/mode
@property (nonatomic) FLEXExplorerMode currentMode;
@@ -61,22 +65,109 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// A colored transparent overlay to indicate that the view is selected.
@property (nonatomic) UIView *selectedViewOverlay;
/// Used to actuate changes in view selection on iOS 10+
@property (nonatomic, readonly) UISelectionFeedbackGenerator *selectionFBG API_AVAILABLE(ios(10.0));
/// self.view.window as a \c FLEXWindow
@property (nonatomic, readonly) FLEXWindow *window;
/// All views that we're KVOing. Used to help us clean up properly.
@property (nonatomic) NSMutableSet<UIView *> *observedViews;
#if !TARGET_OS_TV
/// Used to actuate changes in view selection on iOS 10+
@property (nonatomic, readonly) UISelectionFeedbackGenerator *selectionFBG API_AVAILABLE(ios(10.0));
/// Used to preserve the target app's UIMenuController items.
@property (nonatomic) NSArray<UIMenuItem *> *appMenuItems;
#endif
@property CGPoint lastTouchLocation;
@end
@implementation FLEXExplorerViewController
#pragma mark - Cursor Input
#if TARGET_OS_TV
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
for (UIPress *press in presses) {
if (press.type == UIPressTypeMenu) {
} else {
[super pressesBegan:presses withEvent:event];
}
}
}
-(void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
FXLog(@"presses.anyObject.type: %lu", presses.anyObject.type);
if (self.currentMode != FLEXExplorerModeSelect && self.currentMode != FLEXExplorerModeMove){
[super pressesEnded:presses withEvent:event];
return;
}
CGPoint point = [self.view convertPoint:cursorView.frame.origin toView:nil];
FXLog(@"clicked point: %@", NSStringFromCGPoint(point));
if (self.currentMode == FLEXExplorerModeSelect){
[self updateOutlineViewsForSelectionPoint:point];
}
if (presses.anyObject.type == UIPressTypeMenu) {
if (self.currentMode == FLEXExplorerModeMove){
self.currentMode = FLEXExplorerModeSelect;
cursorView.hidden = false;
} else if (self.currentMode == FLEXExplorerModeSelect){
self.currentMode = FLEXExplorerModeDefault;
cursorView.hidden = true;
[self enableToolbar];
}
} else if (presses.anyObject.type == UIPressTypeUpArrow) {
} else if (presses.anyObject.type == UIPressTypeDownArrow) {
} else if (presses.anyObject.type == UIPressTypeSelect) {
} else if (presses.anyObject.type == UIPressTypePlayPause){
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.lastTouchLocation = CGPointMake(-1, -1);
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
CGPoint location = [touch locationInView:self.view];
if(self.lastTouchLocation.x == -1 && self.lastTouchLocation.y == -1)
{
// Prevent cursor from recentering
self.lastTouchLocation = location;
}
else
{
CGFloat xDiff = location.x - self.lastTouchLocation.x;
CGFloat yDiff = location.y - self.lastTouchLocation.y;
CGRect rect = cursorView.frame;
if(rect.origin.x + xDiff >= 0 && rect.origin.x + xDiff <= 1920)
rect.origin.x += xDiff;//location.x - self.startPos.x;//+= xDiff; //location.x;
if(rect.origin.y + yDiff >= 0 && rect.origin.y + yDiff <= 1080)
rect.origin.y += yDiff;//location.y - self.startPos.y;//+= yDiff; //location.y;
cursorView.frame = rect;
self.lastTouchLocation = location;
}
// We only use one touch, break the loop
break;
}
}
#endif
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
@@ -93,42 +184,132 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)viewDidLoad {
[super viewDidLoad];
// Toolbar
_explorerToolbar = [FLEXExplorerToolbar new];
// Start the toolbar off below any bars that may be at the top of the view.
CGFloat toolbarOriginY = NSUserDefaults.standardUserDefaults.flex_toolbarTopMargin;
CGRect safeArea = [self viewSafeArea];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(
CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea)
)];
CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea)
)];
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(
CGRectGetMinX(safeArea), toolbarOriginY, toolbarSize.width, toolbarSize.height
)];
CGRectGetMinX(safeArea), toolbarOriginY, toolbarSize.width, toolbarSize.height
)];
self.explorerToolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleBottomMargin |
UIViewAutoresizingFlexibleTopMargin;
UIViewAutoresizingFlexibleBottomMargin |
UIViewAutoresizingFlexibleTopMargin;
[self.view addSubview:self.explorerToolbar];
[self setupToolbarActions];
[self setupToolbarGestures];
// View selection
UITapGestureRecognizer *selectionTapGR = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleSelectionTap:)
];
initWithTarget:self action:@selector(handleSelectionTap:)
];
[self.view addGestureRecognizer:selectionTapGR];
// View moving
self.movePanGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleMovePan:)];
self.movePanGR.enabled = self.currentMode == FLEXExplorerModeMove;
[self.view addGestureRecognizer:self.movePanGR];
#if !TARGET_OS_TV
// Feedback
if (@available(iOS 10.0, *)) {
_selectionFBG = [UISelectionFeedbackGenerator new];
}
#else
cursorView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
cursorView.center = CGPointMake(CGRectGetMidX([UIScreen mainScreen].bounds), CGRectGetMidY([UIScreen mainScreen].bounds));
cursorView.image = [FLEXResources cursorImage];
cursorView.backgroundColor = [UIColor clearColor];
cursorView.hidden = YES;
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
longPress.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause], [NSNumber numberWithInteger:UIPressTypeSelect]];
[self.view addGestureRecognizer:longPress];
[self.view addSubview:cursorView];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause], [NSNumber numberWithInteger:UIPressTypeSelect]];
doubleTap.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTap];
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
rightTap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypeRightArrow]];
[self.view addGestureRecognizer:rightTap];
#endif
}
- (void)doubleTap:(UITapGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateEnded) {
if (self.currentMode == FLEXExplorerModeSelect || self.currentMode == FLEXExplorerModeMove){
[self showTVOSOptionsAlert];
}
}
}
- (void)showObjectControllerForSelectedView {
FLEXObjectExplorerViewController *viewExplorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:self.selectedView];
if (!viewExplorer) return;
if ([self presentedViewController]){
FLEXHierarchyViewController *vc = (FLEXHierarchyViewController*)[self presentedViewController];
if ([vc respondsToSelector:@selector(pushViewController:animated:)]){
[vc pushViewController:viewExplorer animated:true];
}
} else {
[self toggleViewsToolWithCompletion:^{
FLEXHierarchyViewController *vc = (FLEXHierarchyViewController*)[self presentedViewController];
if ([vc respondsToSelector:@selector(pushViewController:animated:)]){
[vc pushViewController:viewExplorer animated:true];
}
}];
}
}
- (void)showTVOSOptionsAlert {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"What would you like to do?");
make.button(@"Show Details").handler(^(NSArray<NSString *> *strings) {
[self showObjectControllerForSelectedView];
[[FLEXManager sharedManager] showExplorer];
});
if (self.currentMode == FLEXExplorerModeMove){
make.button(@"Select View").handler(^(NSArray<NSString *> *strings) {
self.currentMode = FLEXExplorerModeSelect;
cursorView.hidden = false;
[[FLEXManager sharedManager] showExplorer];
});
} else if (self.currentMode == FLEXExplorerModeSelect){
make.button(@"Move View").handler(^(NSArray<NSString *> *strings) {
self.currentMode = FLEXExplorerModeMove;
cursorView.hidden = true;
[[FLEXManager sharedManager] showExplorer];
});
}
make.button(@"Show Views").handler(^(NSArray<NSString *> *strings) {
[self toggleViewsTool];
[[FLEXManager sharedManager] showExplorer];
});
make.button(@"Show Usage Hints").handler(^(NSArray<NSString *> *strings) {
[[FLEXManager sharedManager] showHintsAlert];
});
make.button(@"Cancel").cancelStyle().handler(^(NSArray<NSString *> *strings) {
[[FLEXManager sharedManager] showExplorer];
});
} showFrom:self];
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded) {
[self toggleSelectTool];
}
}
- (void)viewWillAppear:(BOOL)animated {
@@ -390,7 +571,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
};
[actionsToItems enumerateKeysAndObjectsUsingBlock:^(NSString *sel, FLEXExplorerToolbarItem *item, BOOL *stop) {
[item addTarget:self action:NSSelectorFromString(sel) forControlEvents:UIControlEventTouchUpInside];
#if !TARGET_OS_TV
[item addTarget:self action:NSSelectorFromString(sel) forControlEvents:UIControlEventTouchUpInside];
#else
[item addTarget:self action:NSSelectorFromString(sel) forControlEvents:UIControlEventPrimaryActionTriggered];
#endif
}];
}
@@ -560,8 +745,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)handleToolbarShowTabsGesture:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
#if !TARGET_OS_TV
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
#endif
// Don't use FLEXNavigationController because the tab viewer itself is not a tab
[super presentViewController:[[UINavigationController alloc]
initWithRootViewController:[FLEXTabsViewController new]
@@ -572,8 +758,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)handleToolbarWindowManagerGesture:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
#if !TARGET_OS_TV
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
#endif
[super presentViewController:[FLEXNavigationController
withRootViewController:[FLEXWindowManagerController new]
] animated:YES completion:nil];
@@ -583,8 +770,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)handleToolbarShowViewControllersGesture:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan && self.viewsAtTapPoint.count) {
// Back up the UIMenuController items since dismissViewController: will attempt to replace them
#if !TARGET_OS_TV
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
#endif
UIViewController *list = [FLEXViewControllersViewController
controllersForViews:self.viewsAtTapPoint
];
@@ -655,9 +843,11 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
- (void)actuateSelectionChangedFeedback {
#if !TARGET_OS_TV
if (@available(iOS 10.0, *)) {
[self.selectionFBG selectionChanged];
}
#endif
}
- (void)updateOutlineViewsForSelectionPoint:(CGPoint)selectionPointInWindow {
@@ -866,7 +1056,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// If we now have a selected view and we didn't have one previously, go to "select" mode.
if (self.currentMode == FLEXExplorerModeDefault && selectedView) {
self.currentMode = FLEXExplorerModeSelect;
//self.currentMode = FLEXExplorerModeSelect;
[self toggleSelectTool];
}
// The selected view setter will also update the selected view overlay appropriately.
@@ -887,12 +1078,12 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
if (!@available(iOS 13, *)) {
[self statusWindow].windowLevel = self.view.window.windowLevel + 1.0;
}
#if !TARGET_OS_TV
// Back up and replace the UIMenuController items
// Edit: no longer replacing the items, but still backing them
// up in case we start replacing them again in the future
self.appMenuItems = UIMenuController.sharedMenuController.menuItems;
#endif
// Show the view controller
[super presentViewController:toPresent animated:animated completion:completion];
}
@@ -900,18 +1091,18 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)dismissViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion {
UIWindow *appWindow = self.window.previousKeyWindow;
[appWindow makeKeyWindow];
#if !TARGET_OS_TV
[appWindow.rootViewController setNeedsStatusBarAppearanceUpdate];
// Restore previous UIMenuController items
// Back up and replace the UIMenuController items
UIMenuController.sharedMenuController.menuItems = self.appMenuItems;
[UIMenuController.sharedMenuController update];
self.appMenuItems = nil;
// Restore the status bar window's normal window level.
// We want it above FLEX while a modal is presented for
// scroll to top, but below FLEX otherwise for exploration.
[self statusWindow].windowLevel = UIWindowLevelStatusBar;
#endif
[self updateButtonStates];
@@ -936,14 +1127,31 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return (id)self.view.window;
}
- (void)disableToolbar {
[self.explorerToolbar setUserInteractionEnabled:false];
[self.explorerToolbar setAlpha:0.5];
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
}
- (void)enableToolbar {
[self.explorerToolbar setUserInteractionEnabled:true];
[self.explorerToolbar setAlpha:1.0];
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
}
#pragma mark - Keyboard Shortcut Helpers
- (void)toggleSelectTool {
if (self.currentMode == FLEXExplorerModeSelect) {
self.currentMode = FLEXExplorerModeDefault;
cursorView.hidden = true;
[self enableToolbar];
} else {
self.currentMode = FLEXExplorerModeSelect;
cursorView.hidden = false;
[self disableToolbar];
}
}
@@ -962,6 +1170,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)toggleViewsToolWithCompletion:(void(^)(void))completion {
[self toggleToolWithViewControllerProvider:^UINavigationController *{
if (self.selectedView) {
FXLog(@"we have a selected view still: %@", self.selectedView);
return [FLEXHierarchyViewController
delegate:self
viewsAtTap:self.viewsAtTapPoint
+2
View File
@@ -19,7 +19,9 @@
// If we make the window level too high, we block out UIAlertViews.
// There's a balance between staying above the app's windows and staying below alerts.
// UIWindowLevelStatusBar + 100 seems to hit that balance.
#if !TARGET_OS_TV
self.windowLevel = UIWindowLevelStatusBar + 100.0;
#endif
}
return self;
}
@@ -10,6 +10,7 @@
#import "FLEXManager+Private.h"
#import "FLEXUtility.h"
#import "FLEXObjectExplorerFactory.h"
#import <TargetConditionals.h>
@interface FLEXWindowManagerController ()
@property (nonatomic) UIWindow *keyWindow;
@@ -161,7 +162,9 @@
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXDetailCell forIndexPath:indexPath];
#if !TARGET_OS_TV
cell.accessoryType = UITableViewCellAccessoryDetailButton;
#endif
cell.textLabel.lineBreakMode = NSLineBreakByTruncatingTail;
UIWindow *window = nil;
@@ -39,7 +39,9 @@
[super viewDidLoad];
self.title = @"Open Tabs";
#if !TARGET_OS_TV
self.navigationController.hidesBarsOnSwipe = NO;
#endif
self.tableView.allowsMultipleSelectionDuringEditing = YES;
[self reloadData:NO];
@@ -106,6 +108,7 @@
- (void)setupDefaultBarItems {
self.navigationItem.rightBarButtonItem = FLEXBarButtonItemSystem(Done, self, @selector(dismissAnimated));
#if !TARGET_OS_TV
self.toolbarItems = @[
UIBarButtonItem.flex_fixedSpace,
UIBarButtonItem.flex_flexibleSpace,
@@ -116,10 +119,12 @@
// Disable editing if no tabs available
self.toolbarItems.lastObject.enabled = self.openTabs.count > 0;
#endif
}
- (void)setupEditingBarItems {
self.navigationItem.rightBarButtonItem = nil;
#if !TARGET_OS_TV
self.toolbarItems = @[
[UIBarButtonItem flex_itemWithTitle:@"Close All" target:self action:@selector(closeAllButtonPressed:)],
UIBarButtonItem.flex_flexibleSpace,
@@ -130,6 +135,7 @@
];
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
#endif
}
- (FLEXExplorerViewController *)corePresenter {
@@ -287,8 +293,10 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.editing) {
// Case: editing with multi-select
#if !TARGET_OS_TV
self.toolbarItems.lastObject.title = @"Close Selected";
self.toolbarItems.lastObject.tintColor = FLEXColor.destructiveColor;
#endif
} else {
if (self.activeIndex == indexPath.row && self.corePresenter != self.presentingViewController) {
// Case: selected the already active tab
@@ -307,8 +315,10 @@
NSParameterAssert(self.editing);
if (tableView.indexPathsForSelectedRows.count == 0) {
#if !TARGET_OS_TV
self.toolbarItems.lastObject.title = @"Done";
self.toolbarItems.lastObject.tintColor = self.view.tintColor;
#endif
}
}
@@ -117,7 +117,9 @@ static const CGFloat kColumnMargin = 1;
UITableView *tableView = [UITableView new];
tableView.delegate = self;
tableView.dataSource = self;
#if !TARGET_OS_TV
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
#endif
[tableView registerClass:[FLEXDBQueryRowCell class]
forCellReuseIdentifier:kFLEXDBQueryRowCellReuse
];
@@ -133,7 +135,9 @@ static const CGFloat kColumnMargin = 1;
UITableView *leftTableView = [UITableView new];
leftTableView.delegate = self;
leftTableView.dataSource = self;
#if !TARGET_OS_TV
leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
#endif
self.leftTableView = leftTableView;
[self addSubview:leftTableView];
@@ -116,7 +116,9 @@
NSString *message = [fields componentsJoinedByString:@"\n\n"];
make.message(message);
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = message;
#endif
});
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
@@ -37,13 +37,17 @@
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(title).message(message);
make.configuredTextField(^(UITextField *textField) {
#if !TARGET_OS_TV
NSString *copied = UIPasteboard.generalPasteboard.string;
#endif
textField.placeholder = @"0x00000070deadbeef";
// Go ahead and paste our clipboard if we have an address copied
#if !TARGET_OS_TV
if ([copied hasPrefix:@"0x"]) {
textField.text = copied;
[textField selectAll:nil];
}
#endif
});
make.button(@"Explore").handler(^(NSArray<NSString *> *strings) {
[host tryExploreAddress:strings.firstObject safely:YES];
@@ -38,10 +38,10 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
self.searchBarDebounceInterval = kFLEXDebounceInstant;
self.showsCarousel = YES;
self.carousel.items = @[@"A→Z", @"Count", @"Size"];
#if !TARGET_OS_TV
self.refreshControl = [UIRefreshControl new];
[self.refreshControl addTarget:self action:@selector(refreshControlDidRefresh:) forControlEvents:UIControlEventValueChanged];
#endif
[self reloadTableData];
}
@@ -101,7 +101,9 @@ static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
- (void)refreshControlDidRefresh:(id)sender {
[self reloadTableData];
#if !TARGET_OS_TV
[self.refreshControl endRefreshing];
#endif
}
- (void)updateHeaderTitle {
@@ -8,6 +8,27 @@
#import <UIKit/UIKit.h>
#if TARGET_OS_TV
@protocol KBWebViewDelegate;
@interface KBWebView: UIView
@property (nullable, nonatomic, assign) id <KBWebViewDelegate> delegate;
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
@property (nullable, nonatomic, readonly, strong) NSURLRequest *request;
- (void)reload;
- (void)stopLoading;
- (void)goBack;
- (void)goForward;
@end
#endif
@interface FLEXWebViewController : UIViewController
- (id)initWithURL:(NSURL *)url;
@@ -8,11 +8,20 @@
#import "FLEXWebViewController.h"
#import "FLEXUtility.h"
#import <TargetConditionals.h>
#if !TARGET_OS_TV
#import <WebKit/WebKit.h>
@interface FLEXWebViewController () <WKNavigationDelegate>
#else
@interface FLEXWebViewController ()
#endif
#if !TARGET_OS_TV
@property (nonatomic) WKWebView *webView;
#else
@property (nonatomic) KBWebView *webView;
#endif
@property (nonatomic) NSString *originalText;
@end
@@ -22,14 +31,20 @@
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
#if !TARGET_OS_TV
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
if (@available(iOS 10.0, *)) {
configuration.dataDetectorTypes = UIDataDetectorTypeLink;
}
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
self.webView.navigationDelegate = self;
#else
self.webView = [[objc_getClass("UIWebView") alloc] initWithFrame:CGRectZero];
self.webView.delegate = self;
#endif
}
return self;
}
@@ -55,9 +70,15 @@
- (void)dealloc {
// WKWebView's delegate is assigned so we need to clear it manually.
#if !TARGET_OS_TV
if (_webView.navigationDelegate == self) {
_webView.navigationDelegate = nil;
}
#else
if (_webView.delegate = self){
_webView.delegate = nil;
}
#endif
}
- (void)viewDidLoad {
@@ -73,10 +94,41 @@
}
- (void)copyButtonTapped:(id)sender {
#if !TARGET_OS_TV
[UIPasteboard.generalPasteboard setString:self.originalText];
#endif
}
#if TARGET_OS_TV
#pragma mark - KBWebView Delegate
-(void) webViewDidStartLoad:(KBWebView *)webView {
LOG_SELF;
}
- (BOOL)webView:(KBWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(NSInteger)navigationType {
FXLog(@"navtype: %lu", navigationType);
FXLog(@"urL: %@", request.URL);
FXLog(@"scheme: %@", request.URL.scheme);
FXLog(@"navigationType: %lu", navigationType);
if (navigationType == 5){//
return YES;
} else {
FLEXWebViewController *webVC = [[[self class] alloc] initWithURL:[request URL]];
webVC.title = [[request URL] absoluteString];
[self.navigationController pushViewController:webVC animated:YES];
return NO;//? maybe?
}
return YES;
}
-(void) webViewDidFinishLoad:(KBWebView *)webView {
LOG_SELF;
}
#else
#pragma mark - WKWebView Delegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
@@ -95,6 +147,7 @@
decisionHandler(policy);
}
#endif
#pragma mark - Class Helpers
@@ -32,7 +32,9 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
@property (nonatomic) NSNumber *recursiveSize;
@property (nonatomic) NSNumber *searchPathsSize;
@property (nonatomic) NSOperationQueue *operationQueue;
#if !TARGET_OS_TV
@property (nonatomic) UIDocumentInteractionController *documentController;
#endif
@property (nonatomic) FLEXFileBrowserSortAttribute sortAttribute;
@end
@@ -97,6 +99,9 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
target:self
action:@selector(sortDidTouchUpInside:)]
]];
#if TARGET_OS_TV
[self addlongPressGestureRecognizer];
#endif
}
- (void)sortDidTouchUpInside:(UIBarButtonItem *)sortButton {
@@ -231,6 +236,68 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
return cell;
}
- (void)addlongPressGestureRecognizer {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
longPress.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeSelect]];
[self.tableView addGestureRecognizer:longPress];
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
rightTap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeRightArrow]];
[self.tableView addGestureRecognizer:rightTap];
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded) {
UITableViewCell *cell = [gesture.view valueForKey:@"_focusedCell"];
[self showActionForCell:cell];
}
}
- (void)fileBrowserOpen:(UITableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
BOOL stillExists = [NSFileManager.defaultManager fileExistsAtPath:self.path isDirectory:NULL];
if (stillExists) {
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title([NSString stringWithFormat:@"Open %@?", fullPath.lastPathComponent]);
make.button(@"OK").handler(^(NSArray<NSString *> *strings) {
FXLog(@"TODO: implement opening files here!");
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
} else {
[FLEXAlert showAlert:@"File Removed" message:@"The file at the specified path no longer exists." from:self];
}
}
//this should only ever be used on tvOS so that #if TARGET_OS is a suffucient fix to keep this from having errors building on iOS
- (void)showActionForCell:(UITableViewCell *)cell {
#if TARGET_OS_TV
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
FXLog(@"showActionForCell: %@", fullPath);
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"Choose an action for this file");
make.button(@"Open").handler(^(NSArray<NSString *> *strings) {
[self fileBrowserOpen:cell];
});
make.button(@"Rename").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
[self fileBrowserRename:cell];
});
make.button(@"Delete").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
[self fileBrowserDelete:cell];
});
if ([FLEXUtility airdropAvailable]){
make.button(@"Share").handler(^(NSArray<NSString *> *strings) {
[self fileBrowserShare:cell];
});
}
make.button(@"Cancel").cancelStyle();
} showFrom:self];
#endif
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
@@ -335,6 +402,7 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
}
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
#if !TARGET_OS_TV
UIMenuItem *rename = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
UIMenuItem *delete = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
UIMenuItem *copyPath = [[UIMenuItem alloc] initWithTitle:@"Copy Path" action:@selector(fileBrowserCopyPath:)];
@@ -343,6 +411,9 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
UIMenuController.sharedMenuController.menuItems = @[rename, delete, copyPath, share];
return YES;
#else
return NO;
#endif
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
@@ -359,10 +430,8 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
}
#if FLEX_AT_LEAST_IOS13_SDK
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
#if !TARGET_OS_TV
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
__weak __typeof__(self) weakSelf = self;
return [UIContextMenuConfiguration configurationWithIdentifier:nil
previewProvider:nil
@@ -395,15 +464,17 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
return [UIMenu menuWithTitle:@"Manage File" image:nil identifier:@"Manage File" options:UIMenuOptionsDisplayInline children:@[rename, delete, copyPath, share]];
}];
}
#endif
#endif
- (void)openFileController:(NSString *)fullPath {
#if !TARGET_OS_TV
UIDocumentInteractionController *controller = [UIDocumentInteractionController new];
controller.URL = [NSURL fileURLWithPath:fullPath];
[controller presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES];
self.documentController = controller;
#endif
}
- (void)fileBrowserRename:(UITableViewCell *)sender {
@@ -456,16 +527,30 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
}
- (void)fileBrowserCopyPath:(UITableViewCell *)sender {
#if !TARGET_OS_TV
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
UIPasteboard.generalPasteboard.string = fullPath;
#endif
}
- (void)fileBrowserShare:(UITableViewCell *)sender {
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *pathString = [self filePathAtIndexPath:indexPath];
NSURL *filePath = [NSURL fileURLWithPath:pathString];
#if TARGET_OS_TV
//This only helps on jailbroken AppleTV - it will allow you to share the files over AirDrop, no share option exists otherwise.
if ([FLEXUtility airdropAvailable]){
[FLEXUtility airDropFile:pathString];
//NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"airdropper://%@", pathString]];
//UIApplication *application = [UIApplication sharedApplication];
//[application openURL:url options:@{} completionHandler:nil];
} else {
[FLEXAlert showAlert:@"Oh no" message:@"A jailbroken AppleTV is required to share files through AirDrop, sorry!" from:self];
}
#else
BOOL isDirectory = NO;
[NSFileManager.defaultManager fileExistsAtPath:pathString isDirectory:&isDirectory];
@@ -474,9 +559,12 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
[self openFileController:pathString];
} else {
// Share sheet for files
UIActivityViewController *shareSheet = [[UIActivityViewController alloc] initWithActivityItems:@[filePath] applicationActivities:nil];
[self presentViewController:shareSheet animated:true completion:nil];
}
#endif
}
- (void)reloadDisplayedPaths {
@@ -542,20 +630,20 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
id target = [self.nextResponder targetForAction:action withSender:sender];
[UIApplication.sharedApplication sendAction:action to:target from:self forEvent:nil];
}
- (void)fileBrowserRename:(UIMenuController *)sender {
//really UIMenuController but this is to silence warnings
- (void)fileBrowserRename:(UIViewController *)sender {
[self forwardAction:_cmd withSender:sender];
}
- (void)fileBrowserDelete:(UIMenuController *)sender {
- (void)fileBrowserDelete:(UIViewController *)sender {
[self forwardAction:_cmd withSender:sender];
}
- (void)fileBrowserCopyPath:(UIMenuController *)sender {
- (void)fileBrowserCopyPath:(UIViewController *)sender {
[self forwardAction:_cmd withSender:sender];
}
- (void)fileBrowserShare:(UIMenuController *)sender {
- (void)fileBrowserShare:(UIViewController *)sender {
[self forwardAction:_cmd withSender:sender];
}
@@ -129,13 +129,17 @@
[self globalsEntryForRow:FLEXGlobalsRowCookies],
],
@(FLEXGlobalsSectionMisc) : @[
#if !TARGET_OS_TV
[self globalsEntryForRow:FLEXGlobalsRowPasteboard],
#endif
[self globalsEntryForRow:FLEXGlobalsRowMainScreen],
[self globalsEntryForRow:FLEXGlobalsRowCurrentDevice],
[self globalsEntryForRow:FLEXGlobalsRowURLSession],
[self globalsEntryForRow:FLEXGlobalsRowURLCache],
[self globalsEntryForRow:FLEXGlobalsRowNotificationCenter],
#if !TARGET_OS_TV
[self globalsEntryForRow:FLEXGlobalsRowMenuController],
#endif
[self globalsEntryForRow:FLEXGlobalsRowFileManager],
[self globalsEntryForRow:FLEXGlobalsRowTimeZone],
[self globalsEntryForRow:FLEXGlobalsRowLocale],
@@ -165,8 +169,9 @@
self.title = @"💪 FLEX";
self.showsSearchBar = YES;
self.searchBarDebounceInterval = kFLEXDebounceInstant;
#if !TARGET_OS_TV
self.navigationItem.backBarButtonItem = [UIBarButtonItem flex_backItemWithTitle:@"Back"];
#endif
_manuallyDeselectOnAppear = NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < 10;
}
@@ -234,13 +234,19 @@
make.message(@"\nPassword: ").message(query.password);
make.button(@"Copy Service").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
[UIPasteboard.generalPasteboard flex_copy:query.service];
#endif
});
make.button(@"Copy Account").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
[UIPasteboard.generalPasteboard flex_copy:query.account];
#endif
});
make.button(@"Copy Password").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
[UIPasteboard.generalPasteboard flex_copy:query.password];
#endif
});
make.button(@"Dismiss").cancelStyle();
@@ -33,7 +33,11 @@
}
+ (instancetype)buttonWithTitle:(NSString *)title action:(FLEXKBToolbarAction)eventHandler {
#if !TARGET_OS_TV
return [self buttonWithTitle:title action:eventHandler forControlEvents:UIControlEventTouchUpInside];
#else
return [self buttonWithTitle:title action:eventHandler forControlEvents:UIControlEventPrimaryActionTriggered];
#endif
}
- (id)initWithTitle:(NSString *)title {
@@ -310,7 +310,11 @@
];
if (self.bundlesOrClasses.count) {
#if !TARGET_OS_TV
cell.accessoryType = UITableViewCellAccessoryDetailButton;
#else
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
#endif
cell.textLabel.text = self.bundlesOrClasses[indexPath.row];
cell.detailTextLabel.text = nil;
if (self.keyPath.classKey) {
@@ -108,8 +108,9 @@
case UIKeyboardAppearanceDefault:
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13, *)) {
#if !TARGET_OS_TV
borderColor = UIColor.systemBackgroundColor;
#endif
if (self.usingDarkMode) {
// style = UIBlurEffectStyleSystemThickMaterial;
backgroundColor = darkColor;
@@ -91,7 +91,9 @@
make.message(path);
make.button(@"Copy Path").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = path;
#endif
});
make.button(@"Dismiss").cancelStyle();
} showFrom:self];
@@ -26,8 +26,10 @@ NSString *const kFLEXSystemLogCellIdentifier = @"FLEXSystemLogCellIdentifier";
self.logMessageLabel = [UILabel new];
self.logMessageLabel.numberOfLines = 0;
#if !TARGET_OS_TV
self.separatorInset = UIEdgeInsetsZero;
self.selectionStyle = UITableViewCellSelectionStyleNone;
#endif
[self.contentView addSubview:self.logMessageLabel];
}
@@ -111,8 +111,9 @@ static BOOL my_os_log_shim_enabled(void *addr) {
} else {
_logController = [FLEXASLLogController withUpdateHandler:logHandler];
}
#if !TARGET_OS_TV
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
#endif
self.title = @"Waiting for Logs...";
// Toolbar buttons //
@@ -265,12 +266,14 @@ static BOOL my_os_log_shim_enabled(void *addr) {
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
// We usually only want to copy the log message itself, not any metadata associated with it.
UIPasteboard.generalPasteboard.string = self.logMessages.filteredList[indexPath.row].messageText ?: @"";
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = self.logMessages.filteredList[indexPath.row].messageText;
#endif
}
}
#if FLEX_AT_LEAST_IOS13_SDK
#if !TARGET_OS_TV
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView
contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
point:(CGPoint)point __IOS_AVAILABLE(13.0) {
@@ -288,7 +291,7 @@ contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
return [UIMenu menuWithTitle:@"" image:nil identifier:nil options:UIMenuOptionsDisplayInline children:@[copy]];
}];
}
#endif
#endif
@end
+4
View File
@@ -25,6 +25,10 @@ NS_ASSUME_NONNULL_BEGIN
- (void)hideExplorer;
- (void)toggleExplorer;
///tvOS specific
- (void)showHintsAlert;
/// Use this to present the explorer in a specific scene when the one
/// it chooses by default is not the one you wish to display it in.
- (void)showExplorerFromScene:(UIWindowScene *)scene API_AVAILABLE(ios(13.0));
+79
View File
@@ -12,6 +12,7 @@
#import "FLEXWindow.h"
#import "FLEXObjectExplorerViewController.h"
#import "FLEXFileBrowserController.h"
#import "NSObject+FLEX_Reflection.h"
@interface FLEXManager () <FLEXWindowEventDelegate, FLEXExplorerViewControllerDelegate>
@@ -57,10 +58,84 @@
return _explorerWindow;
}
- (void)showHintsIfNecessary {
BOOL dontShowHints = [[NSUserDefaults standardUserDefaults] boolForKey:@"DontShowHintsOnLaunch"];
if (!dontShowHints){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self showHintsAlert];
});
}
}
- (void)showHintsAlert {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:@"Usage Guide"
message:@"Triple tap 'Play/Pause' to toggle explorer view visibility.\nIn selection mode 'Menu' will re-enable the toolbar, and a long press on 'Select', 'Play/Pause' or a 'Right siri tap' triggers a contextual menu for view info & movement.\nBrowsing object details long press on 'Select' or a 'Right siri tap' triggers a detail editor.\nInjecting into HeadBoard BREAKS SIRI REMOTE. Suggest AirMagic to navigate."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *hideForeverAction = [UIAlertAction
actionWithTitle:@"Don't Show This Again"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action)
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"DontShowHintsOnLaunch"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self showExplorer];
}];
UIAlertAction *showForeverAction = [UIAlertAction
actionWithTitle:@"Always Show On Launch"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action)
{
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"DontShowHintsOnLaunch"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self showExplorer];
}];
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:@"Dismiss"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
[self showExplorer];
}];
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DontShowHintsOnLaunch"]) {
[alertController addAction:showForeverAction];
}
else {
[alertController addAction:hideForeverAction];
}
[alertController addAction:cancelAction];
#if TARGET_OS_TV
[[self topViewController] presentViewController:alertController animated:YES completion:nil];
#endif
}
- (void)tripleTap:(UITapGestureRecognizer *)tapRecognizer {
FXLog(@"triple tap!");
if ([self isHidden]){
[self showExplorer];
} else {
[self hideExplorer];
}
}
- (void)_addTVOSGestureRecognizer:(UIViewController *)explorer {
UITapGestureRecognizer *tripleTaps = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tripleTap:)];
tripleTaps.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause]];
tripleTaps.numberOfTapsRequired = 3;
[explorer.view addGestureRecognizer:tripleTaps];
}
- (FLEXExplorerViewController *)explorerViewController {
if (!_explorerViewController) {
_explorerViewController = [FLEXExplorerViewController new];
_explorerViewController.delegate = self;
#if TARGET_OS_TV
[self _addTVOSGestureRecognizer:_explorerViewController];
#endif
}
return _explorerViewController;
@@ -69,6 +144,10 @@
- (void)showExplorer {
UIWindow *flex = self.explorerWindow;
flex.hidden = NO;
#if TARGET_OS_TV
FLEXWindow *exp = [self explorerWindow];
[exp makeKeyWindow];
#endif
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
// Only look for a new scene if we don't have one
@@ -64,9 +64,12 @@
registerClass:[FLEXNetworkTransactionCell class]
forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier
];
#if !TARGET_OS_TV
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
#else
[self addlongPressGestureRecognizer];
#endif
self.tableView.rowHeight = FLEXNetworkTransactionCell.preferredCellHeight;
[self registerForNotifications];
[self updateTransactions];
}
@@ -386,12 +389,49 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = request.URL.absoluteString ?: @"";
#endif
}
}
#if FLEX_AT_LEAST_IOS13_SDK
- (void)addlongPressGestureRecognizer {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
longPress.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeSelect]];
[self.tableView addGestureRecognizer:longPress];
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
rightTap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeRightArrow]];
[self.tableView addGestureRecognizer:rightTap];
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded) {
UITableViewCell *cell = [gesture.view valueForKey:@"_focusedCell"];
[self showActionForCell:cell];
}
}
- (void)showActionForCell:(UITableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
[FLEXAlert makeAlert:^(FLEXAlert *make) {
make.title(@"");
make.button([NSString stringWithFormat:@"Blacklist '%@'", request.URL.host]).destructiveStyle().handler(^(NSArray<NSString *> *strings) {
NSMutableArray *blacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist;
[blacklist addObject:request.URL.host];
[FLEXNetworkRecorder.defaultRecorder clearBlacklistedTransactions];
[FLEXNetworkRecorder.defaultRecorder synchronizeBlacklist];
[self tryUpdateTransactions];
});
make.button(@"Cancel").cancelStyle();
} showFrom:self];
}
#if FLEX_AT_LEAST_IOS13_SDK
#if !TARGET_OS_TV
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
NSURLRequest *request = [self transactionAtIndexPath:indexPath].request;
return [UIContextMenuConfiguration
@@ -426,7 +466,7 @@
}
];
}
#endif
#endif
- (FLEXNetworkTransaction *)transactionAtIndexPath:(NSIndexPath *)indexPath {
@@ -12,15 +12,26 @@
#import "FLEXTableView.h"
#import "FLEXColor.h"
#import "NSUserDefaults+FLEX.h"
#import "fakes.h"
#if !TARGET_OS_TV
@interface FLEXNetworkSettingsController () <UIActionSheetDelegate>
#else
@interface FLEXNetworkSettingsController ()
#endif
@property (nonatomic) float cacheLimitValue;
@property (nonatomic, readonly) NSString *cacheLimitCellTitle;
#if !TARGET_OS_TV
@property (nonatomic, readonly) UISwitch *observerSwitch;
@property (nonatomic, readonly) UISwitch *cacheMediaSwitch;
@property (nonatomic, readonly) UISwitch *jsonViewerSwitch;
@property (nonatomic, readonly) UISlider *cacheLimitSlider;
#else
@property (nonatomic, readonly) UIFakeSwitch *observerSwitch;
@property (nonatomic, readonly) UIFakeSwitch *cacheMediaSwitch;
@property (nonatomic, readonly) UIFakeSwitch *jsonViewerSwitch;
@property (nonatomic, readonly) KBSlider *cacheLimitSlider;
#endif
@property (nonatomic) UILabel *cacheLimitLabel;
@property (nonatomic) NSMutableArray<NSString *> *hostBlacklist;
@@ -35,11 +46,17 @@
self.hostBlacklist = FLEXNetworkRecorder.defaultRecorder.hostBlacklist.mutableCopy;
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
#if !TARGET_OS_TV
_observerSwitch = [UISwitch new];
_cacheMediaSwitch = [UISwitch new];
_jsonViewerSwitch = [UISwitch new];
_cacheLimitSlider = [UISlider new];
#else
_observerSwitch = [UIFakeSwitch newSwitch];
_cacheMediaSwitch = [UIFakeSwitch newSwitch];
_jsonViewerSwitch = [UIFakeSwitch newSwitch];
_cacheLimitSlider = [[KBSlider alloc] initWithFrame:CGRectMake(0, 0, 400, 53)];
#endif
self.observerSwitch.on = FLEXNetworkObserver.enabled;
[self.observerSwitch addTarget:self
@@ -64,7 +81,7 @@
forControlEvents:UIControlEventValueChanged
];
UISlider *slider = self.cacheLimitSlider;
KBSlider *slider = self.cacheLimitSlider;
self.cacheLimitValue = FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit;
const NSUInteger fiftyMega = 50 * 1024 * 1024;
slider.minimumValue = 0;
@@ -87,19 +104,19 @@
#pragma mark - Settings Actions
- (void)networkDebuggingToggled:(UISwitch *)sender {
- (void)networkDebuggingToggled:(UIFakeSwitch *)sender {
FLEXNetworkObserver.enabled = sender.isOn;
}
- (void)cacheMediaResponsesToggled:(UISwitch *)sender {
- (void)cacheMediaResponsesToggled:(UIFakeSwitch*)sender {
FLEXNetworkRecorder.defaultRecorder.shouldCacheMediaResponses = sender.isOn;
}
- (void)jsonViewerSettingToggled:(UISwitch *)sender {
- (void)jsonViewerSettingToggled:(UIFakeSwitch *)sender {
[NSUserDefaults.standardUserDefaults flex_toggleBoolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
}
- (void)cacheLimitAdjusted:(UISlider *)sender {
- (void)cacheLimitAdjusted:(KBSlider *)sender {
self.cacheLimitValue = sender.value;
}
@@ -172,7 +189,7 @@
[cell.contentView addSubview:self.cacheLimitSlider];
CGRect container = cell.contentView.frame;
UISlider *slider = self.cacheLimitSlider;
KBSlider *slider = self.cacheLimitSlider;
[slider sizeToFit];
CGFloat sliderWidth = 150.f;
@@ -167,6 +167,9 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
}
+ (CGFloat)preferredCellHeight {
#if TARGET_OS_TV
return 95.0;
#endif
return 65.0;
}
@@ -61,6 +61,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
name:kFLEXNetworkRecorderTransactionUpdatedNotification
object:nil
];
#if !TARGET_OS_TV
self.toolbarItems = @[
UIBarButtonItem.flex_flexibleSpace,
[UIBarButtonItem
@@ -69,6 +70,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
action:@selector(copyButtonPressed:)
]
];
#endif
}
return self;
}
@@ -129,7 +131,9 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
}
- (void)copyButtonPressed:(id)sender {
#if !TARGET_OS_TV
[UIPasteboard.generalPasteboard setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
#endif
}
#pragma mark - Table view data source
@@ -207,12 +211,14 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
FLEXNetworkDetailRow *row = [self rowModelAtIndexPath:indexPath];
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = row.detailText;
#endif
}
}
#if FLEX_AT_LEAST_IOS13_SDK
#if !TARGET_OS_TV
- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point __IOS_AVAILABLE(13.0) {
return [UIContextMenuConfiguration
configurationWithIdentifier:nil
@@ -235,7 +241,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
}
];
}
#endif
#endif
#pragma mark - View Configuration
@@ -161,15 +161,23 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
case FLEXGlobalsRowCurrentDevice:
return [self explorerViewControllerForObject:UIDevice.currentDevice];
case FLEXGlobalsRowPasteboard:
#if !TARGET_OS_TV
return [self explorerViewControllerForObject:UIPasteboard.generalPasteboard];
case FLEXGlobalsRowURLSession:
#else
return nil; //FIXME: Probably not a safe fix
#endif
case FLEXGlobalsRowURLSession:
return [self explorerViewControllerForObject:NSURLSession.sharedSession];
case FLEXGlobalsRowURLCache:
return [self explorerViewControllerForObject:NSURLCache.sharedURLCache];
case FLEXGlobalsRowNotificationCenter:
return [self explorerViewControllerForObject:NSNotificationCenter.defaultCenter];
case FLEXGlobalsRowMenuController:
#if !TARGET_OS_TV
return [self explorerViewControllerForObject:UIMenuController.sharedMenuController];
#else
return nil; //FIXME: Probably not a safe fix
#endif
case FLEXGlobalsRowFileManager:
return [self explorerViewControllerForObject:NSFileManager.defaultManager];
case FLEXGlobalsRowTimeZone:
@@ -187,14 +195,14 @@ static NSMutableDictionary<id<NSCopying>, Class> *classesToRegisteredSections =
case FLEXGlobalsRowKeyWindow:
return [FLEXObjectExplorerFactory
explorerViewControllerForObject:FLEXUtility.appKeyWindow
];
explorerViewControllerForObject:FLEXUtility.appKeyWindow
];
case FLEXGlobalsRowRootViewController: {
id<UIApplicationDelegate> delegate = UIApplication.sharedApplication.delegate;
if ([delegate respondsToSelector:@selector(window)]) {
return [self explorerViewControllerForObject:delegate.window.rootViewController];
}
return nil;
}
default: return nil;
@@ -26,9 +26,15 @@
#import "FLEXShortcutsSection.h"
#import "NSUserDefaults+FLEX.h"
#import <objc/runtime.h>
#import <TargetConditionals.h>
#if TARGET_OS_TV
#import "fakes.h"
#endif
#pragma mark - Private properties
@interface FLEXObjectExplorerViewController () <UIGestureRecognizerDelegate>
@interface FLEXObjectExplorerViewController () <UIGestureRecognizerDelegate>{
BOOL _addedSwipeGestures;
}
@property (nonatomic, readonly) FLEXSingleRowSection *descriptionSection;
@property (nonatomic, readonly) FLEXTableViewSection *customSection;
@property (nonatomic) NSIndexSet *customSectionVisibleIndexes;
@@ -131,10 +137,38 @@
object:nil
];
}
#if TARGET_OS_TV
[self addlongPressGestureRecognizer];
#endif
}
- (void)addlongPressGestureRecognizer {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
longPress.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeSelect]];
[self.tableView addGestureRecognizer:longPress];
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
rightTap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeRightArrow]];
[self.tableView addGestureRecognizer:rightTap];
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded) {
FXLog(@"do something different for long press!");
UITableView *tv = [self tableView];
//naughty naughty
NSIndexPath *focus = [tv valueForKey:@"_focusedCellIndexPath"];
UITableViewCell *cell = [tv valueForKey:@"_focusedCell"];
FXLog(@"focusedIndexPath: %@ accessoryType: %lu", focus, cell.accessoryType);
if (cell.accessoryType != TVTableViewCellAccessoryDisclosureIndicator){
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:focus];
}
}
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
#if !TARGET_OS_TV
[self.navigationController setToolbarHidden:NO animated:YES];
#endif
return YES;
}
@@ -223,10 +257,14 @@
[FLEXBookmarkManager.bookmarks addObject:self.object];
});
make.button(@"Copy Description").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = self.explorer.objectDescription;
#endif
});
make.button(@"Copy Address").handler(^(NSArray<NSString *> *strings) {
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = [FLEXUtility addressOfObject:self.object];
#endif
});
make.button(@"Cancel").cancelStyle();
} showFrom:self source:sender];
@@ -248,11 +286,13 @@
case UISwipeGestureRecognizerDirectionRight:
if (self.selectedScope > 0) {
self.selectedScope -= 1;
[self.tableView reloadData];
}
break;
case UISwipeGestureRecognizerDirectionLeft:
if (self.selectedScope != self.explorer.classHierarchy.count - 1) {
self.selectedScope += 1;
[self.tableView reloadData];
}
break;
@@ -264,6 +304,7 @@
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)g2 {
// Prioritize important pan gestures over our swipe gesture
#if !TARGET_OS_TV
if ([g2 isKindOfClass:[UIPanGestureRecognizer class]]) {
if (g2 == self.navigationController.interactivePopGestureRecognizer ||
g2 == self.navigationController.barHideOnSwipeGestureRecognizer ||
@@ -271,6 +312,13 @@
return NO;
}
}
#else
if ([g2 isKindOfClass:[UIPanGestureRecognizer class]]) {
if (g2 == self.tableView.panGestureRecognizer) {
return NO;
}
}
#endif
return YES;
}
@@ -374,7 +422,9 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @selector(copy:)) {
#if !TARGET_OS_TV
UIPasteboard.generalPasteboard.string = self.explorer.objectDescription;
#endif
}
}
@@ -9,7 +9,9 @@
#import "FLEXDefaultsContentSection.h"
#import "FLEXDefaultEditorViewController.h"
#import "FLEXUtility.h"
#if TARGET_OS_TV
#import "fakes.h"
#endif
@interface FLEXDefaultsContentSection ()
@property (nonatomic) NSUserDefaults *defaults;
@property (nonatomic) NSArray *keys;
@@ -62,7 +64,11 @@
}
- (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
#if !TARGET_OS_TV
return UITableViewCellAccessoryDetailDisclosureButton;
#else
return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailDisclosureButton;
#endif
}
#pragma mark - Private
@@ -15,6 +15,9 @@
#import "FLEXIvar.h"
#import "NSArray+FLEX.h"
#import "FLEXRuntime+UIKitHelpers.h"
#if TARGET_OS_TV
#import "fakes.h"
#endif
@interface FLEXMetadataSection ()
@property (nonatomic, readonly) FLEXObjectExplorer *explorer;
@@ -161,8 +164,14 @@
- (BOOL)canSelectRow:(NSInteger)row {
UITableViewCellAccessoryType accessory = [self accessoryTypeForRow:row];
#if !TARGET_OS_TV
return accessory == UITableViewCellAccessoryDisclosureIndicator ||
accessory == UITableViewCellAccessoryDetailDisclosureButton;
#else
return accessory == UITableViewCellAccessoryDisclosureIndicator ||
accessory == TVTableViewCellAccessoryDetailDisclosureButton;
#endif
return accessory == UITableViewCellAccessoryDisclosureIndicator;
}
- (NSString *)reuseIdentifierForRow:(NSInteger)row {
@@ -39,11 +39,15 @@
make.title(@"Saving Image…");
}];
[host presentViewController:alert animated:YES completion:nil];
#if !TARGET_OS_TV
// Save the image
UIImageWriteToSavedPhotosAlbum(
image, alert, @selector(flex_image:disSaveWithError::), nil
);
#else
//FIXME: do something else for tvOS
NSLog(@"not saving image to photo album, tvOS!");
#endif
}
accessoryType:^UITableViewCellAccessoryType(id image) {
return UITableViewCellAccessoryDisclosureIndicator;
@@ -198,6 +198,7 @@
@implementation FLEXShortcutsFactory (Activities)
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
#if !TARGET_OS_TV
// Property was added in iOS 10 but we want it on iOS 9 too
FLEXRuntimeUtilityTryAddNonatomicProperty(9, item, UIActivityItemProvider.class, id, PropertyKey(ReadOnly));
@@ -208,6 +209,7 @@
self.append.properties(@[
@"activityItems", @"applicationActivities", @"excludedActivityTypes", @"completionHandler"
]).forClass(UIActivityViewController.class);
#endif
}
@end
@@ -17,6 +17,10 @@
#import "FLEXMethod.h"
#import "FLEXRuntime+UIKitHelpers.h"
#import "FLEXObjectExplorer.h"
#if TARGET_OS_TV
#import "fakes.h"
#endif
#pragma mark Private
@@ -185,8 +189,12 @@
- (BOOL)canSelectRow:(NSInteger)row {
UITableViewCellAccessoryType type = [self.shortcuts[row] accessoryTypeWith:self.object];
BOOL hasDisclosure = NO;
hasDisclosure |= type == UITableViewCellAccessoryDisclosureIndicator;
hasDisclosure |= type == UITableViewCellAccessoryDisclosureIndicator;
#if !TARGET_OS_TV
hasDisclosure |= type == UITableViewCellAccessoryDetailDisclosureButton;
#else
hasDisclosure |= type == TVTableViewCellAccessoryDetailDisclosureButton;
#endif
return hasDisclosure;
}
+16 -2
View File
@@ -46,7 +46,11 @@
// Drag handle
self.dragHandle = [UIView new];
self.dragHandle.backgroundColor = UIColor.clearColor;
#if !TARGET_OS_TV
self.dragHandleImageView = [[UIImageView alloc] initWithImage:FLEXResources.dragHandle];
#else
self.dragHandleImageView = [[UIImageView alloc] initWithImage:nil];
#endif
self.dragHandleImageView.tintColor = [FLEXColor.iconColor colorWithAlphaComponent:0.666];
[self.dragHandle addSubview:self.dragHandleImageView];
[self addSubview:self.dragHandle];
@@ -99,15 +103,21 @@
dragHandleImageFrame.origin.y = FLEXFloor((self.dragHandle.frame.size.height - dragHandleImageFrame.size.height) / 2.0);
self.dragHandleImageView.frame = dragHandleImageFrame;
CGFloat itemPadding = 0;
#if TARGET_OS_TV
itemPadding = 40;
#endif
// Toolbar Items
CGFloat originX = CGRectGetMaxX(self.dragHandle.frame);
CGFloat originY = CGRectGetMinY(safeArea);
CGFloat height = kToolbarItemHeight;
CGFloat width = FLEXFloor((CGRectGetWidth(safeArea) - CGRectGetWidth(self.dragHandle.frame)) / self.toolbarItems.count);
width = width - itemPadding;
for (FLEXExplorerToolbarItem *toolbarItem in self.toolbarItems) {
toolbarItem.currentItem.frame = CGRectMake(originX, originY, width, height);
originX = CGRectGetMaxX(toolbarItem.currentItem.frame);
originX = CGRectGetMaxX(toolbarItem.currentItem.frame) + itemPadding;
}
// Make sure the last toolbar item goes to the edge to account for any accumulated rounding effects.
@@ -145,8 +155,9 @@
selectedViewColorFrame.origin.x = kHorizontalPadding;
selectedViewColorFrame.origin.y = FLEXFloor((kDescriptionContainerHeight - kSelectedViewColorDiameter) / 2.0);
self.selectedViewColorIndicator.frame = selectedViewColorFrame;
#if !TARGET_OS_TV
self.selectedViewColorIndicator.layer.cornerRadius = ceil(selectedViewColorFrame.size.height / 2.0);
#endif
// Selected View Description
CGRect descriptionLabelFrame = CGRectZero;
CGFloat descriptionOriginX = CGRectGetMaxX(selectedViewColorFrame) + kHorizontalPadding;
@@ -210,6 +221,9 @@
}
+ (CGFloat)toolbarItemHeight {
#if TARGET_OS_TV
return 68.0;
#endif
return 44.0;
}
+20 -3
View File
@@ -31,7 +31,9 @@
}
+ (instancetype)itemWithTitle:(NSString *)title image:(UIImage *)image sibling:(FLEXExplorerToolbarItem *)backupItem {
#if !TARGET_OS_TV
NSParameterAssert(title); NSParameterAssert(image);
#endif
FLEXExplorerToolbarItem *toolbarItem = [self buttonWithType:UIButtonTypeSystem];
toolbarItem.sibling = backupItem;
@@ -42,7 +44,9 @@
toolbarItem.titleLabel.font = [UIFont systemFontOfSize:12.0];
[toolbarItem setTitle:title forState:UIControlStateNormal];
[toolbarItem setImage:image forState:UIControlStateNormal];
#if !TARGET_OS_TV
[toolbarItem setTitleColor:FLEXColor.primaryTextColor forState:UIControlStateNormal];
#endif
[toolbarItem setTitleColor:FLEXColor.deemphasizedTextColor forState:UIControlStateDisabled];
return toolbarItem;
}
@@ -75,7 +79,7 @@
}
+ (CGFloat)topMargin {
return 2.0;
return 20.0;
}
@@ -118,7 +122,9 @@
if (self.highlighted) {
self.backgroundColor = self.class.highlightedBackgroundColor;
} else if (self.selected) {
#if !TARGET_OS_TV
self.backgroundColor = self.class.selectedBackgroundColor;
#endif
} else {
self.backgroundColor = self.class.defaultBackgroundColor;
}
@@ -128,6 +134,13 @@
#pragma mark - UIButton Layout Overrides
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
CGFloat padding = 0;
CGFloat titleBottomPadding = 0;
#if TARGET_OS_TV
padding = 30;
titleBottomPadding = 5;
#endif
NSDictionary *attrs = [[self class] titleAttributes];
// Bottom aligned and centered.
CGRect titleRect = CGRectZero;
@@ -137,11 +150,13 @@
context:nil].size;
titleSize = CGSizeMake(ceil(titleSize.width), ceil(titleSize.height));
titleRect.size = titleSize;
titleRect.origin.y = contentRect.origin.y + CGRectGetMaxY(contentRect) - titleSize.height;
titleRect.origin.x = contentRect.origin.x + FLEXFloor((contentRect.size.width - titleSize.width) / 2.0);
titleRect.origin.y = contentRect.origin.y + CGRectGetMaxY(contentRect) - titleSize.height - titleBottomPadding;
titleRect.origin.x = contentRect.origin.x + FLEXFloor((contentRect.size.width-padding - titleSize.width) / 2.0);
return titleRect;
}
#if !TARGET_OS_TV
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
CGSize imageSize = self.image.size;
CGRect titleRect = [self titleRectForContentRect:contentRect];
@@ -152,4 +167,6 @@
return imageRect;
}
#endif
@end
@@ -17,6 +17,9 @@
#import "FLEXUtility.h"
#import "NSArray+FLEX.h"
#import "NSString+FLEX.h"
#if TARGET_OS_TV
#import "fakes.h"
#endif
#define FLEXObjectExplorerDefaultsImpl \
- (FLEXObjectExplorerDefaults *)defaults { \
@@ -110,7 +113,11 @@ FLEXObjectExplorerDefaultsImpl
if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
if (self.defaults.isEditable) {
// Editable non-nil value, both
#if !TARGET_OS_TV
return UITableViewCellAccessoryDetailDisclosureButton;
#else
return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailDisclosureButton;
#endif
} else {
// Uneditable non-nil value, chevron only
return UITableViewCellAccessoryDisclosureIndicator;
@@ -118,7 +125,11 @@ FLEXObjectExplorerDefaultsImpl
} else {
if (self.defaults.isEditable) {
// Editable nil value, just (i)
#if !TARGET_OS_TV
return UITableViewCellAccessoryDetailButton;
#else
return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailButton;
#endif
} else {
// Non-editable nil value, neither
return UITableViewCellAccessoryNone;
@@ -244,7 +255,11 @@ FLEXObjectExplorerDefaultsImpl
if ([self getPotentiallyUnboxedValue:object]) {
if (self.defaults.isEditable) {
// Editable non-nil value, both
#if !TARGET_OS_TV
return UITableViewCellAccessoryDetailDisclosureButton;
#else
return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailDisclosureButton;
#endif
} else {
// Uneditable non-nil value, chevron only
return UITableViewCellAccessoryDisclosureIndicator;
@@ -252,7 +267,11 @@ FLEXObjectExplorerDefaultsImpl
} else {
if (self.defaults.isEditable) {
// Editable nil value, just (i)
#if !TARGET_OS_TV
return UITableViewCellAccessoryDetailButton;
#else
return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailButton;
#endif
} else {
// Non-editable nil value, neither
return UITableViewCellAccessoryNone;
@@ -8,11 +8,17 @@
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@class FLEXMirror, FLEXMethod, FLEXIvar, FLEXProperty, FLEXMethodBase, FLEXPropertyAttributes, FLEXProtocol;
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (tvOS)
- (UIViewController *)topViewController;
- (BOOL)darkMode;
@end
/// Returns the type encoding string given the encoding for the return type and parameters, if any.
/// @discussion Example usage for a \c void returning method which takes
/// an \c int: @code FLEXTypeEncoding(@encode(void), @encode(int));
@@ -17,7 +17,17 @@
#import "FLEXPropertyAttributes.h"
#import "NSArray+FLEX.h"
#import "FLEXUtility.h"
#import "UIWindow+FLEX.h"
@implementation NSObject (tvOS)
- (UIViewController *)topViewController {
return [[[UIApplication sharedApplication] keyWindow] visibleViewController];
}
- (BOOL)darkMode {
return [[[self topViewController] view] darkMode];
}
@end
NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...) {
if (returnType == NULL) return nil;
@@ -8,6 +8,22 @@
#import "UIView+FLEX_Layout.h"
@implementation UIView (darkMode)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
#pragma clang diagnostic ignored "-Wunguarded-availability"
- (BOOL)darkMode {
if ([[self traitCollection] respondsToSelector:@selector(userInterfaceStyle)]){
return ([[self traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark);
} else {
return false;
}
return false;
}
#pragma clang diagnostic pop
@end
@implementation UIView (FLEX_Layout)
- (void)flex_centerInView:(UIView *)view {
+4 -2
View File
@@ -7,9 +7,11 @@
//
#import "UIFont+FLEX.h"
#if TARGET_OS_TV
#define kFLEXDefaultCellFontSize 24.0
#else
#define kFLEXDefaultCellFontSize 12.0
#endif
@implementation UIFont (FLEX)
+ (UIFont *)flex_defaultTableCellFont {
@@ -0,0 +1,27 @@
//
// UIView+FLEX_Layout.h
// FLEX
//
// Created by Tanner Bennett on 7/18/19.
// Copyright © 2020 FLEX Team. All rights reserved.
//
#import <UIKit/UIKit.h>
#define Padding(p) UIEdgeInsetsMake(p, p, p, p)
@interface UIView (darkMode)
- (BOOL)darkMode;
@end
@interface UIView (FLEX_Layout)
- (void)centerInView:(UIView *)view;
- (void)pinEdgesTo:(UIView *)view;
- (void)pinEdgesTo:(UIView *)view withInsets:(UIEdgeInsets)insets;
- (void)pinEdgesToSuperview;
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets aboveView:(UIView *)sibling;
- (void)pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets belowView:(UIView *)sibling;
@end
@@ -0,0 +1,17 @@
//
// UIWindow+FLEX.h
// FLEX
//
// Created by Kevin Bradley on 12/23/20.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIWindow (FLEX)
- (UIViewController *)visibleViewController;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,33 @@
//
// UIWindow+FLEX.m
// FLEX
//
// Created by Kevin Bradley on 12/23/20.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "UIWindow+FLEX.h"
@implementation UIWindow (FLEX)
- (UIViewController *)visibleViewController {
UIViewController *rootViewController = self.rootViewController;
return [UIWindow getVisibleViewControllerFrom:rootViewController];
}
+ (UIViewController *) getVisibleViewControllerFrom:(UIViewController *) vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [UIWindow getVisibleViewControllerFrom:[((UINavigationController *) vc) visibleViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [UIWindow getVisibleViewControllerFrom:[((UITabBarController *) vc) selectedViewController]];
} else {
if (vc.presentedViewController) {
return [UIWindow getVisibleViewControllerFrom:vc.presentedViewController];
} else {
return vc;
}
}
}
@end
+2
View File
@@ -9,6 +9,8 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#define UIColorFromRGB(rgbValue, alp) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:alp]
NS_ASSUME_NONNULL_BEGIN
@interface FLEXColor : NSObject
+24 -12
View File
@@ -8,6 +8,15 @@
#import "FLEXColor.h"
#import "FLEXUtility.h"
#import <UIKit/UIInterface.h>
/**
tvOS notes: alot of these properties are marked as unavailable on tvOS, not only is that a bald faced lie, but without using these values the UI gets completely screwy!
i initially tried to do some creative macro undef/redef but that didnt work, so going to use good ole KVC, valueForKey: works on class methods as well! and this will work
perfectly without needing to patch another SDK file for this to build for tvOS
*/
#if FLEX_AT_LEAST_IOS13_SDK
#define FLEXDynamicColor(dynamic, static) ({ \
@@ -28,7 +37,7 @@
#pragma mark - Background Colors
+ (UIColor *)primaryBackgroundColor {
return FLEXDynamicColor(systemBackgroundColor, whiteColor);
return FLEXDynamicColor(valueForKey:@"systemBackgroundColor", whiteColor);
}
+ (UIColor *)primaryBackgroundColorWithAlpha:(CGFloat)alpha {
@@ -37,7 +46,7 @@
+ (UIColor *)secondaryBackgroundColor {
return FLEXDynamicColor(
secondarySystemBackgroundColor,
valueForKey:@"secondarySystemBackgroundColor",
colorWithHue:2.0/3.0 saturation:0.02 brightness:0.97 alpha:1
);
}
@@ -50,7 +59,7 @@
// All the background/fill colors are varying shades
// of white and black with really low alpha levels.
// We use systemGray4Color instead to avoid alpha issues.
return FLEXDynamicColor(systemGray4Color, lightGrayColor);
return FLEXDynamicColor(valueForKey:@"systemGray4Color", lightGrayColor);
}
+ (UIColor *)tertiaryBackgroundColorWithAlpha:(CGFloat)alpha {
@@ -59,7 +68,7 @@
+ (UIColor *)groupedBackgroundColor {
return FLEXDynamicColor(
systemGroupedBackgroundColor,
valueForKey:@"systemGroupedBackgroundColor",
colorWithHue:2.0/3.0 saturation:0.02 brightness:0.97 alpha:1
);
}
@@ -69,7 +78,7 @@
}
+ (UIColor *)secondaryGroupedBackgroundColor {
return FLEXDynamicColor(secondarySystemGroupedBackgroundColor, whiteColor);
return FLEXDynamicColor(valueForKey:@"secondarySystemGroupedBackgroundColor", whiteColor);
}
+ (UIColor *)secondaryGroupedBackgroundColorWithAlpha:(CGFloat)alpha {
@@ -83,7 +92,7 @@
}
+ (UIColor *)deemphasizedTextColor {
return FLEXDynamicColor(secondaryLabelColor, lightGrayColor);
return FLEXDynamicColor(valueForKey:@"secondaryLabelColor", lightGrayColor);
}
#pragma mark - UI Element Colors
@@ -91,9 +100,12 @@
+ (UIColor *)tintColor {
#if FLEX_AT_LEAST_IOS13_SDK
if (@available(iOS 13.0, *)) {
return UIColor.systemBlueColor;
return [UIColor valueForKey:@"systemBlueColor"];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return UIApplication.sharedApplication.keyWindow.tintColor;
#pragma clang diagnostic pop
}
#else
return UIApplication.sharedApplication.keyWindow.tintColor;
@@ -102,7 +114,7 @@
+ (UIColor *)scrollViewBackgroundColor {
return FLEXDynamicColor(
systemGroupedBackgroundColor,
valueForKey:@"systemGroupedBackgroundColor",
colorWithHue:2.0/3.0 saturation:0.02 brightness:0.95 alpha:1
);
}
@@ -117,24 +129,24 @@
+ (UIColor *)toolbarItemHighlightedColor {
return FLEXDynamicColor(
quaternaryLabelColor,
valueForKey:@"quaternaryLabelColor",
colorWithHue:2.0/3.0 saturation:0.1 brightness:0.25 alpha:0.6
);
}
+ (UIColor *)toolbarItemSelectedColor {
return FLEXDynamicColor(
secondaryLabelColor,
valueForKey:@"secondaryLabelColor",
colorWithHue:2.0/3.0 saturation:0.1 brightness:0.25 alpha:0.68
);
}
+ (UIColor *)hairlineColor {
return FLEXDynamicColor(systemGray3Color, colorWithWhite:0.75 alpha:1);
return FLEXDynamicColor(valueForKey:@"systemGray3Color", colorWithWhite:0.75 alpha:1);
}
+ (UIColor *)destructiveColor {
return FLEXDynamicColor(systemRedColor, redColor);
return FLEXDynamicColor(valueForKey:@"systemRedColor", redColor);
}
@end
+5
View File
@@ -85,4 +85,9 @@ NS_INLINE CGRect FLEXRectSetHeight(CGRect r, CGFloat height) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, \
(int64_t)(nSeconds * NSEC_PER_SEC)), onQueue, block)
#define DLog(format, ...) CFShow((__bridge CFStringRef)[NSString stringWithFormat:format, ## __VA_ARGS__]);
#define LOG_SELF NSLog(@"[FLEXLog] %@ %@", self, NSStringFromSelector(_cmd))
#define FXLog(format, ...) NSLog(@"[FLEXLog] %@",[NSString stringWithFormat:format, ## __VA_ARGS__]);
#define DLOG_SELF DLog(@"%@ %@", self, NSStringFromSelector(_cmd))
#endif /* FLEXMacros_h */
+1 -1
View File
@@ -55,5 +55,5 @@
@property (readonly, class) UIImage *checkerPattern;
@property (readonly, class) UIColor *checkerPatternColor;
@property (readonly, class) UIImage *hierarchyIndentPattern;
@property (readonly, class) UIImage *cursorImage;
@end
+396 -4
View File
@@ -8687,25 +8687,414 @@ static const u_int8_t FLEXHierarchyIndentPattern3x[] = {
0xf4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
static const u_int8_t FLEXCursor[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x00,
0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b,
0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x0a, 0x4f, 0x69, 0x43,
0x43, 0x50, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x73, 0x68, 0x6f, 0x70, 0x20,
0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x00,
0x00, 0x78, 0xda, 0x9d, 0x53, 0x67, 0x54, 0x53, 0xe9, 0x16, 0x3d, 0xf7,
0xde, 0xf4, 0x42, 0x4b, 0x88, 0x80, 0x94, 0x4b, 0x6f, 0x52, 0x15, 0x08,
0x20, 0x52, 0x42, 0x8b, 0x80, 0x14, 0x91, 0x26, 0x2a, 0x21, 0x09, 0x10,
0x4a, 0x88, 0x21, 0xa1, 0xd9, 0x15, 0x51, 0xc1, 0x11, 0x45, 0x45, 0x04,
0x1b, 0xc8, 0xa0, 0x88, 0x03, 0x8e, 0x8e, 0x80, 0x8c, 0x15, 0x51, 0x2c,
0x0c, 0x8a, 0x0a, 0xd8, 0x07, 0xe4, 0x21, 0xa2, 0x8e, 0x83, 0xa3, 0x88,
0x8a, 0xca, 0xfb, 0xe1, 0x7b, 0xa3, 0x6b, 0xd6, 0xbc, 0xf7, 0xe6, 0xcd,
0xfe, 0xb5, 0xd7, 0x3e, 0xe7, 0xac, 0xf3, 0x9d, 0xb3, 0xcf, 0x07, 0xc0,
0x08, 0x0c, 0x96, 0x48, 0x33, 0x51, 0x35, 0x80, 0x0c, 0xa9, 0x42, 0x1e,
0x11, 0xe0, 0x83, 0xc7, 0xc4, 0xc6, 0xe1, 0xe4, 0x2e, 0x40, 0x81, 0x0a,
0x24, 0x70, 0x00, 0x10, 0x08, 0xb3, 0x64, 0x21, 0x73, 0xfd, 0x23, 0x01,
0x00, 0xf8, 0x7e, 0x3c, 0x3c, 0x2b, 0x22, 0xc0, 0x07, 0xbe, 0x00, 0x01,
0x78, 0xd3, 0x0b, 0x08, 0x00, 0xc0, 0x4d, 0x9b, 0xc0, 0x30, 0x1c, 0x87,
0xff, 0x0f, 0xea, 0x42, 0x99, 0x5c, 0x01, 0x80, 0x84, 0x01, 0xc0, 0x74,
0x91, 0x38, 0x4b, 0x08, 0x80, 0x14, 0x00, 0x40, 0x7a, 0x8e, 0x42, 0xa6,
0x00, 0x40, 0x46, 0x01, 0x80, 0x9d, 0x98, 0x26, 0x53, 0x00, 0xa0, 0x04,
0x00, 0x60, 0xcb, 0x63, 0x62, 0xe3, 0x00, 0x50, 0x2d, 0x00, 0x60, 0x27,
0x7f, 0xe6, 0xd3, 0x00, 0x80, 0x9d, 0xf8, 0x99, 0x7b, 0x01, 0x00, 0x5b,
0x94, 0x21, 0x15, 0x01, 0xa0, 0x91, 0x00, 0x20, 0x13, 0x65, 0x88, 0x44,
0x00, 0x68, 0x3b, 0x00, 0xac, 0xcf, 0x56, 0x8a, 0x45, 0x00, 0x58, 0x30,
0x00, 0x14, 0x66, 0x4b, 0xc4, 0x39, 0x00, 0xd8, 0x2d, 0x00, 0x30, 0x49,
0x57, 0x66, 0x48, 0x00, 0xb0, 0xb7, 0x00, 0xc0, 0xce, 0x10, 0x0b, 0xb2,
0x00, 0x08, 0x0c, 0x00, 0x30, 0x51, 0x88, 0x85, 0x29, 0x00, 0x04, 0x7b,
0x00, 0x60, 0xc8, 0x23, 0x23, 0x78, 0x00, 0x84, 0x99, 0x00, 0x14, 0x46,
0xf2, 0x57, 0x3c, 0xf1, 0x2b, 0xae, 0x10, 0xe7, 0x2a, 0x00, 0x00, 0x78,
0x99, 0xb2, 0x3c, 0xb9, 0x24, 0x39, 0x45, 0x81, 0x5b, 0x08, 0x2d, 0x71,
0x07, 0x57, 0x57, 0x2e, 0x1e, 0x28, 0xce, 0x49, 0x17, 0x2b, 0x14, 0x36,
0x61, 0x02, 0x61, 0x9a, 0x40, 0x2e, 0xc2, 0x79, 0x99, 0x19, 0x32, 0x81,
0x34, 0x0f, 0xe0, 0xf3, 0xcc, 0x00, 0x00, 0xa0, 0x91, 0x15, 0x11, 0xe0,
0x83, 0xf3, 0xfd, 0x78, 0xce, 0x0e, 0xae, 0xce, 0xce, 0x36, 0x8e, 0xb6,
0x0e, 0x5f, 0x2d, 0xea, 0xbf, 0x06, 0xff, 0x22, 0x62, 0x62, 0xe3, 0xfe,
0xe5, 0xcf, 0xab, 0x70, 0x40, 0x00, 0x00, 0xe1, 0x74, 0x7e, 0xd1, 0xfe,
0x2c, 0x2f, 0xb3, 0x1a, 0x80, 0x3b, 0x06, 0x80, 0x6d, 0xfe, 0xa2, 0x25,
0xee, 0x04, 0x68, 0x5e, 0x0b, 0xa0, 0x75, 0xf7, 0x8b, 0x66, 0xb2, 0x0f,
0x40, 0xb5, 0x00, 0xa0, 0xe9, 0xda, 0x57, 0xf3, 0x70, 0xf8, 0x7e, 0x3c,
0x3c, 0x45, 0xa1, 0x90, 0xb9, 0xd9, 0xd9, 0xe5, 0xe4, 0xe4, 0xd8, 0x4a,
0xc4, 0x42, 0x5b, 0x61, 0xca, 0x57, 0x7d, 0xfe, 0x67, 0xc2, 0x5f, 0xc0,
0x57, 0xfd, 0x6c, 0xf9, 0x7e, 0x3c, 0xfc, 0xf7, 0xf5, 0xe0, 0xbe, 0xe2,
0x24, 0x81, 0x32, 0x5d, 0x81, 0x47, 0x04, 0xf8, 0xe0, 0xc2, 0xcc, 0xf4,
0x4c, 0xa5, 0x1c, 0xcf, 0x92, 0x09, 0x84, 0x62, 0xdc, 0xe6, 0x8f, 0x47,
0xfc, 0xb7, 0x0b, 0xff, 0xfc, 0x1d, 0xd3, 0x22, 0xc4, 0x49, 0x62, 0xb9,
0x58, 0x2a, 0x14, 0xe3, 0x51, 0x12, 0x71, 0x8e, 0x44, 0x9a, 0x8c, 0xf3,
0x32, 0xa5, 0x22, 0x89, 0x42, 0x92, 0x29, 0xc5, 0x25, 0xd2, 0xff, 0x64,
0xe2, 0xdf, 0x2c, 0xfb, 0x03, 0x3e, 0xdf, 0x35, 0x00, 0xb0, 0x6a, 0x3e,
0x01, 0x7b, 0x91, 0x2d, 0xa8, 0x5d, 0x63, 0x03, 0xf6, 0x4b, 0x27, 0x10,
0x58, 0x74, 0xc0, 0xe2, 0xf7, 0x00, 0x00, 0xf2, 0xbb, 0x6f, 0xc1, 0xd4,
0x28, 0x08, 0x03, 0x80, 0x68, 0x83, 0xe1, 0xcf, 0x77, 0xff, 0xef, 0x3f,
0xfd, 0x47, 0xa0, 0x25, 0x00, 0x80, 0x66, 0x49, 0x92, 0x71, 0x00, 0x00,
0x5e, 0x44, 0x24, 0x2e, 0x54, 0xca, 0xb3, 0x3f, 0xc7, 0x08, 0x00, 0x00,
0x44, 0xa0, 0x81, 0x2a, 0xb0, 0x41, 0x1b, 0xf4, 0xc1, 0x18, 0x2c, 0xc0,
0x06, 0x1c, 0xc1, 0x05, 0xdc, 0xc1, 0x0b, 0xfc, 0x60, 0x36, 0x84, 0x42,
0x24, 0xc4, 0xc2, 0x42, 0x10, 0x42, 0x0a, 0x64, 0x80, 0x1c, 0x72, 0x60,
0x29, 0xac, 0x82, 0x42, 0x28, 0x86, 0xcd, 0xb0, 0x1d, 0x2a, 0x60, 0x2f,
0xd4, 0x40, 0x1d, 0x34, 0xc0, 0x51, 0x68, 0x86, 0x93, 0x70, 0x0e, 0x2e,
0xc2, 0x55, 0xb8, 0x0e, 0x3d, 0x70, 0x0f, 0xfa, 0x61, 0x08, 0x9e, 0xc1,
0x28, 0xbc, 0x81, 0x09, 0x04, 0x41, 0xc8, 0x08, 0x13, 0x61, 0x21, 0xda,
0x88, 0x01, 0x62, 0x8a, 0x58, 0x23, 0x8e, 0x08, 0x17, 0x99, 0x85, 0xf8,
0x21, 0xc1, 0x48, 0x04, 0x12, 0x8b, 0x24, 0x20, 0xc9, 0x88, 0x14, 0x51,
0x22, 0x4b, 0x91, 0x35, 0x48, 0x31, 0x52, 0x8a, 0x54, 0x20, 0x55, 0x48,
0x1d, 0xf2, 0x3d, 0x72, 0x02, 0x39, 0x87, 0x5c, 0x46, 0xba, 0x91, 0x3b,
0xc8, 0x00, 0x32, 0x82, 0xfc, 0x86, 0xbc, 0x47, 0x31, 0x94, 0x81, 0xb2,
0x51, 0x3d, 0xd4, 0x0c, 0xb5, 0x43, 0xb9, 0xa8, 0x37, 0x1a, 0x84, 0x46,
0xa2, 0x0b, 0xd0, 0x64, 0x74, 0x31, 0x9a, 0x8f, 0x16, 0xa0, 0x9b, 0xd0,
0x72, 0xb4, 0x1a, 0x3d, 0x8c, 0x36, 0xa1, 0xe7, 0xd0, 0xab, 0x68, 0x0f,
0xda, 0x8f, 0x3e, 0x43, 0xc7, 0x30, 0xc0, 0xe8, 0x18, 0x07, 0x33, 0xc4,
0x6c, 0x30, 0x2e, 0xc6, 0xc3, 0x42, 0xb1, 0x38, 0x2c, 0x09, 0x93, 0x63,
0xcb, 0xb1, 0x22, 0xac, 0x0c, 0xab, 0xc6, 0x1a, 0xb0, 0x56, 0xac, 0x03,
0xbb, 0x89, 0xf5, 0x63, 0xcf, 0xb1, 0x77, 0x04, 0x12, 0x81, 0x45, 0xc0,
0x09, 0x36, 0x04, 0x77, 0x42, 0x20, 0x61, 0x1e, 0x41, 0x48, 0x58, 0x4c,
0x58, 0x4e, 0xd8, 0x48, 0xa8, 0x20, 0x1c, 0x24, 0x34, 0x11, 0xda, 0x09,
0x37, 0x09, 0x03, 0x84, 0x51, 0xc2, 0x27, 0x22, 0x93, 0xa8, 0x4b, 0xb4,
0x26, 0xba, 0x11, 0xf9, 0xc4, 0x18, 0x62, 0x32, 0x31, 0x87, 0x58, 0x48,
0x2c, 0x23, 0xd6, 0x12, 0x8f, 0x13, 0x2f, 0x10, 0x7b, 0x88, 0x43, 0xc4,
0x37, 0x24, 0x12, 0x89, 0x43, 0x32, 0x27, 0xb9, 0x90, 0x02, 0x49, 0xb1,
0xa4, 0x54, 0xd2, 0x12, 0xd2, 0x46, 0xd2, 0x6e, 0x52, 0x23, 0xe9, 0x2c,
0xa9, 0x9b, 0x34, 0x48, 0x1a, 0x23, 0x93, 0xc9, 0xda, 0x64, 0x6b, 0xb2,
0x07, 0x39, 0x94, 0x2c, 0x20, 0x2b, 0xc8, 0x85, 0xe4, 0x9d, 0xe4, 0xc3,
0xe4, 0x33, 0xe4, 0x1b, 0xe4, 0x21, 0xf2, 0x5b, 0x0a, 0x9d, 0x62, 0x40,
0x71, 0xa4, 0xf8, 0x53, 0xe2, 0x28, 0x52, 0xca, 0x6a, 0x4a, 0x19, 0xe5,
0x10, 0xe5, 0x34, 0xe5, 0x06, 0x65, 0x98, 0x32, 0x41, 0x55, 0xa3, 0x9a,
0x52, 0xdd, 0xa8, 0xa1, 0x54, 0x11, 0x35, 0x8f, 0x5a, 0x42, 0xad, 0xa1,
0xb6, 0x52, 0xaf, 0x51, 0x87, 0xa8, 0x13, 0x34, 0x75, 0x9a, 0x39, 0xcd,
0x83, 0x16, 0x49, 0x4b, 0xa5, 0xad, 0xa2, 0x95, 0xd3, 0x1a, 0x68, 0x17,
0x68, 0xf7, 0x69, 0xaf, 0xe8, 0x74, 0xba, 0x11, 0xdd, 0x95, 0x1e, 0x4e,
0x97, 0xd0, 0x57, 0xd2, 0xcb, 0xe9, 0x47, 0xe8, 0x97, 0xe8, 0x03, 0xf4,
0x77, 0x0c, 0x0d, 0x86, 0x15, 0x83, 0xc7, 0x88, 0x67, 0x28, 0x19, 0x9b,
0x18, 0x07, 0x18, 0x67, 0x19, 0x77, 0x18, 0xaf, 0x98, 0x4c, 0xa6, 0x19,
0xd3, 0x8b, 0x19, 0xc7, 0x54, 0x30, 0x37, 0x31, 0xeb, 0x98, 0xe7, 0x99,
0x0f, 0x99, 0x6f, 0x55, 0x58, 0x2a, 0xb6, 0x2a, 0x7c, 0x15, 0x91, 0xca,
0x0a, 0x95, 0x4a, 0x95, 0x26, 0x95, 0x1b, 0x2a, 0x2f, 0x54, 0xa9, 0xaa,
0xa6, 0xaa, 0xde, 0xaa, 0x0b, 0x55, 0xf3, 0x55, 0xcb, 0x54, 0x8f, 0xa9,
0x5e, 0x53, 0x7d, 0xae, 0x46, 0x55, 0x33, 0x53, 0xe3, 0xa9, 0x09, 0xd4,
0x96, 0xab, 0x55, 0xaa, 0x9d, 0x50, 0xeb, 0x53, 0x1b, 0x53, 0x67, 0xa9,
0x3b, 0xa8, 0x87, 0xaa, 0x67, 0xa8, 0x6f, 0x54, 0x3f, 0xa4, 0x7e, 0x59,
0xfd, 0x89, 0x06, 0x59, 0xc3, 0x4c, 0xc3, 0x4f, 0x43, 0xa4, 0x51, 0xa0,
0xb1, 0x5f, 0xe3, 0xbc, 0xc6, 0x20, 0x0b, 0x63, 0x19, 0xb3, 0x78, 0x2c,
0x21, 0x6b, 0x0d, 0xab, 0x86, 0x75, 0x81, 0x35, 0xc4, 0x26, 0xb1, 0xcd,
0xd9, 0x7c, 0x76, 0x2a, 0xbb, 0x98, 0xfd, 0x1d, 0xbb, 0x8b, 0x3d, 0xaa,
0xa9, 0xa1, 0x39, 0x43, 0x33, 0x4a, 0x33, 0x57, 0xb3, 0x52, 0xf3, 0x94,
0x66, 0x3f, 0x07, 0xe3, 0x98, 0x71, 0xf8, 0x9c, 0x74, 0x4e, 0x09, 0xe7,
0x28, 0xa7, 0x97, 0xf3, 0x7e, 0x8a, 0xde, 0x14, 0xef, 0x29, 0xe2, 0x29,
0x1b, 0xa6, 0x34, 0x4c, 0xb9, 0x31, 0x65, 0x5c, 0x6b, 0xaa, 0x96, 0x97,
0x96, 0x58, 0xab, 0x48, 0xab, 0x51, 0xab, 0x47, 0xeb, 0xbd, 0x36, 0xae,
0xed, 0xa7, 0x9d, 0xa6, 0xbd, 0x45, 0xbb, 0x59, 0xfb, 0x81, 0x0e, 0x41,
0xc7, 0x4a, 0x27, 0x5c, 0x27, 0x47, 0x67, 0x8f, 0xce, 0x05, 0x9d, 0xe7,
0x53, 0xd9, 0x53, 0xdd, 0xa7, 0x0a, 0xa7, 0x16, 0x4d, 0x3d, 0x3a, 0xf5,
0xae, 0x2e, 0xaa, 0x6b, 0xa5, 0x1b, 0xa1, 0xbb, 0x44, 0x77, 0xbf, 0x6e,
0xa7, 0xee, 0x98, 0x9e, 0xbe, 0x5e, 0x80, 0x9e, 0x4c, 0x6f, 0xa7, 0xde,
0x79, 0xbd, 0xe7, 0xfa, 0x1c, 0x7d, 0x2f, 0xfd, 0x54, 0xfd, 0x6d, 0xfa,
0xa7, 0xf5, 0x47, 0x0c, 0x58, 0x06, 0xb3, 0x0c, 0x24, 0x06, 0xdb, 0x0c,
0xce, 0x18, 0x3c, 0xc5, 0x35, 0x71, 0x6f, 0x3c, 0x1d, 0x2f, 0xc7, 0xdb,
0xf1, 0x51, 0x43, 0x5d, 0xc3, 0x40, 0x43, 0xa5, 0x61, 0x95, 0x61, 0x97,
0xe1, 0x84, 0x91, 0xb9, 0xd1, 0x3c, 0xa3, 0xd5, 0x46, 0x8d, 0x46, 0x0f,
0x8c, 0x69, 0xc6, 0x5c, 0xe3, 0x24, 0xe3, 0x6d, 0xc6, 0x6d, 0xc6, 0xa3,
0x26, 0x06, 0x26, 0x21, 0x26, 0x4b, 0x4d, 0xea, 0x4d, 0xee, 0x9a, 0x52,
0x4d, 0xb9, 0xa6, 0x29, 0xa6, 0x3b, 0x4c, 0x3b, 0x4c, 0xc7, 0xcd, 0xcc,
0xcd, 0xa2, 0xcd, 0xd6, 0x99, 0x35, 0x9b, 0x3d, 0x31, 0xd7, 0x32, 0xe7,
0x9b, 0xe7, 0x9b, 0xd7, 0x9b, 0xdf, 0xb7, 0x60, 0x5a, 0x78, 0x5a, 0x2c,
0xb6, 0xa8, 0xb6, 0xb8, 0x65, 0x49, 0xb2, 0xe4, 0x5a, 0xa6, 0x59, 0xee,
0xb6, 0xbc, 0x6e, 0x85, 0x5a, 0x39, 0x59, 0xa5, 0x58, 0x55, 0x5a, 0x5d,
0xb3, 0x46, 0xad, 0x9d, 0xad, 0x25, 0xd6, 0xbb, 0xad, 0xbb, 0xa7, 0x11,
0xa7, 0xb9, 0x4e, 0x93, 0x4e, 0xab, 0x9e, 0xd6, 0x67, 0xc3, 0xb0, 0xf1,
0xb6, 0xc9, 0xb6, 0xa9, 0xb7, 0x19, 0xb0, 0xe5, 0xd8, 0x06, 0xdb, 0xae,
0xb6, 0x6d, 0xb6, 0x7d, 0x61, 0x67, 0x62, 0x17, 0x67, 0xb7, 0xc5, 0xae,
0xc3, 0xee, 0x93, 0xbd, 0x93, 0x7d, 0xba, 0x7d, 0x8d, 0xfd, 0x3d, 0x07,
0x0d, 0x87, 0xd9, 0x0e, 0xab, 0x1d, 0x5a, 0x1d, 0x7e, 0x73, 0xb4, 0x72,
0x14, 0x3a, 0x56, 0x3a, 0xde, 0x9a, 0xce, 0x9c, 0xee, 0x3f, 0x7d, 0xc5,
0xf4, 0x96, 0xe9, 0x2f, 0x67, 0x58, 0xcf, 0x10, 0xcf, 0xd8, 0x33, 0xe3,
0xb6, 0x13, 0xcb, 0x29, 0xc4, 0x69, 0x9d, 0x53, 0x9b, 0xd3, 0x47, 0x67,
0x17, 0x67, 0xb9, 0x73, 0x83, 0xf3, 0x88, 0x8b, 0x89, 0x4b, 0x82, 0xcb,
0x2e, 0x97, 0x3e, 0x2e, 0x9b, 0x1b, 0xc6, 0xdd, 0xc8, 0xbd, 0xe4, 0x4a,
0x74, 0xf5, 0x71, 0x5d, 0xe1, 0x7a, 0xd2, 0xf5, 0x9d, 0x9b, 0xb3, 0x9b,
0xc2, 0xed, 0xa8, 0xdb, 0xaf, 0xee, 0x36, 0xee, 0x69, 0xee, 0x87, 0xdc,
0x9f, 0xcc, 0x34, 0x9f, 0x29, 0x9e, 0x59, 0x33, 0x73, 0xd0, 0xc3, 0xc8,
0x43, 0xe0, 0x51, 0xe5, 0xd1, 0x3f, 0x0b, 0x9f, 0x95, 0x30, 0x6b, 0xdf,
0xac, 0x7e, 0x4f, 0x43, 0x4f, 0x81, 0x67, 0xb5, 0xe7, 0x23, 0x2f, 0x63,
0x2f, 0x91, 0x57, 0xad, 0xd7, 0xb0, 0xb7, 0xa5, 0x77, 0xaa, 0xf7, 0x61,
0xef, 0x17, 0x3e, 0xf6, 0x3e, 0x72, 0x9f, 0xe3, 0x3e, 0xe3, 0x3c, 0x37,
0xde, 0x32, 0xde, 0x59, 0x5f, 0xcc, 0x37, 0xc0, 0xb7, 0xc8, 0xb7, 0xcb,
0x4f, 0xc3, 0x6f, 0x9e, 0x5f, 0x85, 0xdf, 0x43, 0x7f, 0x23, 0xff, 0x64,
0xff, 0x7a, 0xff, 0xd1, 0x00, 0xa7, 0x80, 0x25, 0x01, 0x67, 0x03, 0x89,
0x81, 0x41, 0x81, 0x5b, 0x02, 0xfb, 0xf8, 0x7a, 0x7c, 0x21, 0xbf, 0x8e,
0x3f, 0x3a, 0xdb, 0x65, 0xf6, 0xb2, 0xd9, 0xed, 0x41, 0x8c, 0xa0, 0xb9,
0x41, 0x15, 0x41, 0x8f, 0x82, 0xad, 0x82, 0xe5, 0xc1, 0xad, 0x21, 0x68,
0xc8, 0xec, 0x90, 0xad, 0x21, 0xf7, 0xe7, 0x98, 0xce, 0x91, 0xce, 0x69,
0x0e, 0x85, 0x50, 0x7e, 0xe8, 0xd6, 0xd0, 0x07, 0x61, 0xe6, 0x61, 0x8b,
0xc3, 0x7e, 0x0c, 0x27, 0x85, 0x87, 0x85, 0x57, 0x86, 0x3f, 0x8e, 0x70,
0x88, 0x58, 0x1a, 0xd1, 0x31, 0x97, 0x35, 0x77, 0xd1, 0xdc, 0x43, 0x73,
0xdf, 0x44, 0xfa, 0x44, 0x96, 0x44, 0xde, 0x9b, 0x67, 0x31, 0x4f, 0x39,
0xaf, 0x2d, 0x4a, 0x35, 0x2a, 0x3e, 0xaa, 0x2e, 0x6a, 0x3c, 0xda, 0x37,
0xba, 0x34, 0xba, 0x3f, 0xc6, 0x2e, 0x66, 0x59, 0xcc, 0xd5, 0x58, 0x9d,
0x58, 0x49, 0x6c, 0x4b, 0x1c, 0x39, 0x2e, 0x2a, 0xae, 0x36, 0x6e, 0x6c,
0xbe, 0xdf, 0xfc, 0xed, 0xf3, 0x87, 0xe2, 0x9d, 0xe2, 0x0b, 0xe3, 0x7b,
0x17, 0x98, 0x2f, 0xc8, 0x5d, 0x70, 0x79, 0xa1, 0xce, 0xc2, 0xf4, 0x85,
0xa7, 0x16, 0xa9, 0x2e, 0x12, 0x2c, 0x3a, 0x96, 0x40, 0x4c, 0x88, 0x4e,
0x38, 0x94, 0xf0, 0x41, 0x10, 0x2a, 0xa8, 0x16, 0x8c, 0x25, 0xf2, 0x13,
0x77, 0x25, 0x8e, 0x0a, 0x79, 0xc2, 0x1d, 0xc2, 0x67, 0x22, 0x2f, 0xd1,
0x36, 0xd1, 0x88, 0xd8, 0x43, 0x5c, 0x2a, 0x1e, 0x4e, 0xf2, 0x48, 0x2a,
0x4d, 0x7a, 0x92, 0xec, 0x91, 0xbc, 0x35, 0x79, 0x24, 0xc5, 0x33, 0xa5,
0x2c, 0xe5, 0xb9, 0x84, 0x27, 0xa9, 0x90, 0xbc, 0x4c, 0x0d, 0x4c, 0xdd,
0x9b, 0x3a, 0x9e, 0x16, 0x9a, 0x76, 0x20, 0x6d, 0x32, 0x3d, 0x3a, 0xbd,
0x31, 0x83, 0x92, 0x91, 0x90, 0x71, 0x42, 0xaa, 0x21, 0x4d, 0x93, 0xb6,
0x67, 0xea, 0x67, 0xe6, 0x66, 0x76, 0xcb, 0xac, 0x65, 0x85, 0xb2, 0xfe,
0xc5, 0x6e, 0x8b, 0xb7, 0x2f, 0x1e, 0x95, 0x07, 0xc9, 0x6b, 0xb3, 0x90,
0xac, 0x05, 0x59, 0x2d, 0x0a, 0xb6, 0x42, 0xa6, 0xe8, 0x54, 0x5a, 0x28,
0xd7, 0x2a, 0x07, 0xb2, 0x67, 0x65, 0x57, 0x66, 0xbf, 0xcd, 0x89, 0xca,
0x39, 0x96, 0xab, 0x9e, 0x2b, 0xcd, 0xed, 0xcc, 0xb3, 0xca, 0xdb, 0x90,
0x37, 0x9c, 0xef, 0x9f, 0xff, 0xed, 0x12, 0xc2, 0x12, 0xe1, 0x92, 0xb6,
0xa5, 0x86, 0x4b, 0x57, 0x2d, 0x1d, 0x58, 0xe6, 0xbd, 0xac, 0x6a, 0x39,
0xb2, 0x3c, 0x71, 0x79, 0xdb, 0x0a, 0xe3, 0x15, 0x05, 0x2b, 0x86, 0x56,
0x06, 0xac, 0x3c, 0xb8, 0x8a, 0xb6, 0x2a, 0x6d, 0xd5, 0x4f, 0xab, 0xed,
0x57, 0x97, 0xae, 0x7e, 0xbd, 0x26, 0x7a, 0x4d, 0x6b, 0x81, 0x5e, 0xc1,
0xca, 0x82, 0xc1, 0xb5, 0x01, 0x6b, 0xeb, 0x0b, 0x55, 0x0a, 0xe5, 0x85,
0x7d, 0xeb, 0xdc, 0xd7, 0xed, 0x5d, 0x4f, 0x58, 0x2f, 0x59, 0xdf, 0xb5,
0x61, 0xfa, 0x86, 0x9d, 0x1b, 0x3e, 0x15, 0x89, 0x8a, 0xae, 0x14, 0xdb,
0x17, 0x97, 0x15, 0x7f, 0xd8, 0x28, 0xdc, 0x78, 0xe5, 0x1b, 0x87, 0x6f,
0xca, 0xbf, 0x99, 0xdc, 0x94, 0xb4, 0xa9, 0xab, 0xc4, 0xb9, 0x64, 0xcf,
0x66, 0xd2, 0x66, 0xe9, 0xe6, 0xde, 0x2d, 0x9e, 0x5b, 0x0e, 0x96, 0xaa,
0x97, 0xe6, 0x97, 0x0e, 0x6e, 0x0d, 0xd9, 0xda, 0xb4, 0x0d, 0xdf, 0x56,
0xb4, 0xed, 0xf5, 0xf6, 0x45, 0xdb, 0x2f, 0x97, 0xcd, 0x28, 0xdb, 0xbb,
0x83, 0xb6, 0x43, 0xb9, 0xa3, 0xbf, 0x3c, 0xb8, 0xbc, 0x65, 0xa7, 0xc9,
0xce, 0xcd, 0x3b, 0x3f, 0x54, 0xa4, 0x54, 0xf4, 0x54, 0xfa, 0x54, 0x36,
0xee, 0xd2, 0xdd, 0xb5, 0x61, 0xd7, 0xf8, 0x6e, 0xd1, 0xee, 0x1b, 0x7b,
0xbc, 0xf6, 0x34, 0xec, 0xd5, 0xdb, 0x5b, 0xbc, 0xf7, 0xfd, 0x3e, 0xc9,
0xbe, 0xdb, 0x55, 0x01, 0x55, 0x4d, 0xd5, 0x66, 0xd5, 0x65, 0xfb, 0x49,
0xfb, 0xb3, 0xf7, 0x3f, 0xae, 0x89, 0xaa, 0xe9, 0xf8, 0x96, 0xfb, 0x6d,
0x5d, 0xad, 0x4e, 0x6d, 0x71, 0xed, 0xc7, 0x03, 0xd2, 0x03, 0xfd, 0x07,
0x23, 0x0e, 0xb6, 0xd7, 0xb9, 0xd4, 0xd5, 0x1d, 0xd2, 0x3d, 0x54, 0x52,
0x8f, 0xd6, 0x2b, 0xeb, 0x47, 0x0e, 0xc7, 0x1f, 0xbe, 0xfe, 0x9d, 0xef,
0x77, 0x2d, 0x0d, 0x36, 0x0d, 0x55, 0x8d, 0x9c, 0xc6, 0xe2, 0x23, 0x70,
0x44, 0x79, 0xe4, 0xe9, 0xf7, 0x09, 0xdf, 0xf7, 0x1e, 0x0d, 0x3a, 0xda,
0x76, 0x8c, 0x7b, 0xac, 0xe1, 0x07, 0xd3, 0x1f, 0x76, 0x1d, 0x67, 0x1d,
0x2f, 0x6a, 0x42, 0x9a, 0xf2, 0x9a, 0x46, 0x9b, 0x53, 0x9a, 0xfb, 0x5b,
0x62, 0x5b, 0xba, 0x4f, 0xcc, 0x3e, 0xd1, 0xd6, 0xea, 0xde, 0x7a, 0xfc,
0x47, 0xdb, 0x1f, 0x0f, 0x9c, 0x34, 0x3c, 0x59, 0x79, 0x4a, 0xf3, 0x54,
0xc9, 0x69, 0xda, 0xe9, 0x82, 0xd3, 0x93, 0x67, 0xf2, 0xcf, 0x8c, 0x9d,
0x95, 0x9d, 0x7d, 0x7e, 0x2e, 0xf9, 0xdc, 0x60, 0xdb, 0xa2, 0xb6, 0x7b,
0xe7, 0x63, 0xce, 0xdf, 0x6a, 0x0f, 0x6f, 0xef, 0xba, 0x10, 0x74, 0xe1,
0xd2, 0x45, 0xff, 0x8b, 0xe7, 0x3b, 0xbc, 0x3b, 0xce, 0x5c, 0xf2, 0xb8,
0x74, 0xf2, 0xb2, 0xdb, 0xe5, 0x13, 0x57, 0xb8, 0x57, 0x9a, 0xaf, 0x3a,
0x5f, 0x6d, 0xea, 0x74, 0xea, 0x3c, 0xfe, 0x93, 0xd3, 0x4f, 0xc7, 0xbb,
0x9c, 0xbb, 0x9a, 0xae, 0xb9, 0x5c, 0x6b, 0xb9, 0xee, 0x7a, 0xbd, 0xb5,
0x7b, 0x66, 0xf7, 0xe9, 0x1b, 0x9e, 0x37, 0xce, 0xdd, 0xf4, 0xbd, 0x79,
0xf1, 0x16, 0xff, 0xd6, 0xd5, 0x9e, 0x39, 0x3d, 0xdd, 0xbd, 0xf3, 0x7a,
0x6f, 0xf7, 0xc5, 0xf7, 0xf5, 0xdf, 0x16, 0xdd, 0x7e, 0x72, 0x27, 0xfd,
0xce, 0xcb, 0xbb, 0xd9, 0x77, 0x27, 0xee, 0xad, 0xbc, 0x4f, 0xbc, 0x5f,
0xf4, 0x40, 0xed, 0x41, 0xd9, 0x43, 0xdd, 0x87, 0xd5, 0x3f, 0x5b, 0xfe,
0xdc, 0xd8, 0xef, 0xdc, 0x7f, 0x6a, 0xc0, 0x77, 0xa0, 0xf3, 0xd1, 0xdc,
0x47, 0xf7, 0x06, 0x85, 0x83, 0xcf, 0xfe, 0x91, 0xf5, 0x8f, 0x0f, 0x43,
0x05, 0x8f, 0x99, 0x8f, 0xcb, 0x86, 0x0d, 0x86, 0xeb, 0x9e, 0x38, 0x3e,
0x39, 0x39, 0xe2, 0x3f, 0x72, 0xfd, 0xe9, 0xfc, 0xa7, 0x43, 0xcf, 0x64,
0xcf, 0x26, 0x9e, 0x17, 0xfe, 0xa2, 0xfe, 0xcb, 0xae, 0x17, 0x16, 0x2f,
0x7e, 0xf8, 0xd5, 0xeb, 0xd7, 0xce, 0xd1, 0x98, 0xd1, 0xa1, 0x97, 0xf2,
0x97, 0x93, 0xbf, 0x6d, 0x7c, 0xa5, 0xfd, 0xea, 0xc0, 0xeb, 0x19, 0xaf,
0xdb, 0xc6, 0xc2, 0xc6, 0x1e, 0xbe, 0xc9, 0x78, 0x33, 0x31, 0x5e, 0xf4,
0x56, 0xfb, 0xed, 0xc1, 0x77, 0xdc, 0x77, 0x1d, 0xef, 0xa3, 0xdf, 0x0f,
0x4f, 0xe4, 0x7c, 0x20, 0x7f, 0x28, 0xff, 0x68, 0xf9, 0xb1, 0xf5, 0x53,
0xd0, 0xa7, 0xfb, 0x93, 0x19, 0x93, 0x93, 0xff, 0x04, 0x03, 0x98, 0xf3,
0xfc, 0x63, 0x33, 0x2d, 0xdb, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52,
0x4d, 0x00, 0x00, 0x7a, 0x25, 0x00, 0x00, 0x80, 0x83, 0x00, 0x00, 0xf9,
0xff, 0x00, 0x00, 0x80, 0xe9, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xea,
0x60, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00, 0x17, 0x6f, 0x92, 0x5f, 0xc5,
0x46, 0x00, 0x00, 0x07, 0x2e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec,
0x9a, 0x5f, 0x4c, 0x53, 0x57, 0x1c, 0xc7, 0xbf, 0xf7, 0x16, 0x68, 0x75,
0x74, 0x76, 0x8e, 0x39, 0xaa, 0x93, 0xf8, 0x77, 0x4c, 0x99, 0x58, 0x32,
0x71, 0x4e, 0x59, 0x69, 0x0b, 0xa5, 0x35, 0x50, 0x60, 0xfe, 0x79, 0x58,
0xf4, 0x41, 0x34, 0x31, 0xcb, 0x92, 0xa9, 0xe8, 0x83, 0x59, 0xc2, 0x12,
0xcc, 0x7c, 0x58, 0x7c, 0xf1, 0x61, 0x66, 0x46, 0x8d, 0xc4, 0xbd, 0xb8,
0x07, 0x93, 0x06, 0x6c, 0x97, 0xde, 0x8b, 0xbd, 0xb4, 0x5a, 0xcc, 0x12,
0x2d, 0x6a, 0x93, 0x65, 0x73, 0x18, 0x50, 0x54, 0x50, 0xfc, 0x33, 0xff,
0x00, 0xba, 0xfa, 0x87, 0xb3, 0x07, 0xcf, 0xbd, 0xde, 0x8b, 0x65, 0x54,
0x50, 0x7b, 0xdb, 0x71, 0x92, 0x93, 0xf4, 0xdc, 0x7b, 0xcf, 0x8f, 0xfb,
0xfb, 0x9c, 0xdf, 0xbf, 0x73, 0x2e, 0xa8, 0xad, 0xad, 0xdd, 0xde, 0xd5,
0xd5, 0xf5, 0x17, 0x9e, 0x37, 0x2d, 0x12, 0xd4, 0x08, 0x21, 0x6f, 0xbc,
0xb3, 0x67, 0xcf, 0x9e, 0xbd, 0x94, 0x93, 0x93, 0xf3, 0x21, 0xc7, 0x71,
0x2d, 0xf4, 0x3d, 0xa2, 0x00, 0x32, 0xf0, 0x3f, 0x69, 0x2c, 0xcb, 0xb2,
0x5a, 0x00, 0x70, 0x38, 0x1c, 0x56, 0x8e, 0xe3, 0x5a, 0x01, 0xbc, 0x05,
0xe0, 0xd1, 0xff, 0x05, 0x02, 0xcb, 0x30, 0x4c, 0x9a, 0x38, 0x70, 0x38,
0x1c, 0xcb, 0x42, 0xa1, 0xd0, 0x49, 0xea, 0x06, 0x8f, 0x12, 0xe9, 0x0e,
0x6f, 0x0c, 0xc0, 0xd0, 0x0b, 0x45, 0x45, 0x45, 0x0b, 0x39, 0x8e, 0x0b,
0x02, 0x48, 0xa3, 0xee, 0xa0, 0x4d, 0x75, 0x00, 0xcc, 0xd0, 0x8b, 0x0e,
0x87, 0x63, 0x09, 0xc7, 0x71, 0xc7, 0x01, 0xa4, 0xa7, 0x7a, 0x4c, 0x50,
0x58, 0x80, 0xcf, 0xe7, 0xc3, 0xf5, 0xeb, 0xd7, 0x45, 0x08, 0x4b, 0x79,
0x9e, 0x0f, 0xca, 0xdc, 0x21, 0x23, 0xe5, 0x01, 0x34, 0x34, 0x34, 0x20,
0x3f, 0x3f, 0x5f, 0x1a, 0x97, 0x95, 0x95, 0x2d, 0x0d, 0x04, 0x02, 0xad,
0xa9, 0x1c, 0x13, 0x14, 0x00, 0xf2, 0xf3, 0xf3, 0x71, 0xf3, 0xe6, 0x4d,
0xd8, 0xed, 0x76, 0xe9, 0x9a, 0xc5, 0x62, 0x59, 0xc4, 0xf3, 0x7c, 0x80,
0x2a, 0x9f, 0x72, 0xee, 0xc0, 0x0e, 0x2d, 0x44, 0x00, 0xc0, 0xef, 0xf7,
0x63, 0xf9, 0xf2, 0xe5, 0x72, 0x4b, 0xf8, 0xac, 0xb9, 0xb9, 0x39, 0x25,
0xdd, 0x21, 0x26, 0x00, 0x00, 0xe0, 0x38, 0x0e, 0x4e, 0xa7, 0x53, 0x1a,
0xdb, 0xed, 0xf6, 0x25, 0xa9, 0x08, 0x81, 0xfd, 0xaf, 0x9b, 0x3c, 0xcf,
0x2b, 0x2c, 0xc1, 0x6e, 0xb7, 0x2f, 0xf1, 0xfb, 0xfd, 0xc7, 0xa9, 0xf2,
0x29, 0x01, 0x81, 0x1d, 0xe9, 0x01, 0x8e, 0xe3, 0xe0, 0x70, 0x38, 0xa4,
0x71, 0x49, 0x49, 0xc9, 0xa7, 0x82, 0x20, 0xb4, 0x02, 0xd0, 0xa5, 0x02,
0x04, 0x36, 0x9e, 0x87, 0x9a, 0x9b, 0x9b, 0x51, 0x56, 0x56, 0x26, 0x8d,
0x6d, 0x36, 0x5b, 0x61, 0x4b, 0x4b, 0xcb, 0x89, 0x54, 0x80, 0xc0, 0xc6,
0xfb, 0xe0, 0xb1, 0x63, 0xc7, 0x14, 0xd9, 0xc1, 0x6a, 0xb5, 0x16, 0x06,
0x02, 0x81, 0x10, 0x80, 0x09, 0xc9, 0x0c, 0x81, 0x7d, 0x99, 0x87, 0xfd,
0x7e, 0x3f, 0x4a, 0x4b, 0x4b, 0x15, 0x29, 0xd2, 0xe7, 0xf3, 0xf9, 0xe9,
0x30, 0x29, 0x21, 0xb0, 0x2f, 0x3b, 0x41, 0x10, 0x04, 0xb8, 0x5c, 0x2e,
0x69, 0xec, 0x74, 0x3a, 0x97, 0x1e, 0x3d, 0x7a, 0xb4, 0x25, 0x59, 0x03,
0x23, 0x3b, 0x9a, 0x49, 0x5e, 0xaf, 0x17, 0x56, 0xab, 0x55, 0x1a, 0xbb,
0x5c, 0x2e, 0xab, 0x20, 0x08, 0x49, 0x09, 0x81, 0x1d, 0xed, 0xc4, 0x60,
0x30, 0xa8, 0x80, 0x60, 0xb3, 0xd9, 0x96, 0x09, 0x82, 0x10, 0xa0, 0x1b,
0xa8, 0xa4, 0x81, 0xc0, 0x8e, 0x65, 0x72, 0x30, 0x18, 0x54, 0x14, 0x4b,
0x36, 0x9b, 0x6d, 0x69, 0x63, 0x63, 0xa3, 0x2f, 0x99, 0x62, 0x02, 0x3b,
0x56, 0x01, 0x3c, 0xcf, 0x63, 0xe5, 0xca, 0x95, 0xd2, 0xb8, 0xaa, 0xaa,
0xaa, 0xe4, 0xc8, 0x91, 0x23, 0x3c, 0x95, 0xad, 0x7a, 0x08, 0xec, 0xab,
0x10, 0xe2, 0x76, 0xbb, 0x15, 0xd9, 0x61, 0xd5, 0xaa, 0x55, 0x65, 0x3c,
0xcf, 0x27, 0x45, 0x9d, 0xc0, 0xbe, 0x2a, 0x41, 0x82, 0x20, 0xc0, 0x6c,
0x36, 0xcb, 0x37, 0x50, 0xcb, 0x82, 0xc1, 0xe0, 0x71, 0xb5, 0xef, 0x1d,
0xd8, 0x57, 0x29, 0x2c, 0x14, 0x0a, 0x29, 0xf6, 0x0e, 0xc5, 0xc5, 0xc5,
0x8b, 0xd5, 0x5e, 0x27, 0xb0, 0xaf, 0x5a, 0x20, 0xc7, 0x71, 0xa8, 0xac,
0xac, 0x94, 0xd7, 0x09, 0x45, 0x4d, 0x4d, 0x4d, 0x82, 0x5a, 0x21, 0xb0,
0xaf, 0x43, 0xa8, 0xc7, 0xe3, 0x51, 0x14, 0x4b, 0x95, 0x95, 0x95, 0x36,
0x5a, 0x2c, 0xa9, 0xce, 0x1d, 0xd8, 0xd7, 0x25, 0xd8, 0xeb, 0xf5, 0xc2,
0x66, 0xb3, 0x29, 0x8a, 0xa5, 0x50, 0x28, 0xd4, 0xaa, 0x36, 0x08, 0xec,
0xeb, 0x14, 0x1e, 0x08, 0x04, 0x50, 0x50, 0x50, 0x80, 0xfe, 0xfe, 0x7e,
0x00, 0x40, 0x51, 0x51, 0xd1, 0x22, 0x8f, 0xc7, 0xd3, 0x2c, 0x4b, 0x91,
0x09, 0x3f, 0x63, 0x4c, 0x1b, 0xcb, 0x64, 0x86, 0x61, 0xb0, 0x78, 0xf1,
0x62, 0xf4, 0xf5, 0xf5, 0xc5, 0xbc, 0x4f, 0x08, 0x41, 0x67, 0x67, 0x27,
0x76, 0xed, 0xda, 0x85, 0xfa, 0xfa, 0x7a, 0xb0, 0x2c, 0x8b, 0x8a, 0x8a,
0x0a, 0xb3, 0xc7, 0xe3, 0x11, 0x5c, 0x2e, 0x97, 0x1d, 0xcf, 0xcf, 0x18,
0x1f, 0x03, 0x20, 0x09, 0x21, 0x50, 0x52, 0x52, 0xb2, 0x81, 0xd0, 0x56,
0x5f, 0x5f, 0x4f, 0xe8, 0x8b, 0xc4, 0xd5, 0x19, 0x86, 0x21, 0xad, 0xad,
0xad, 0x64, 0xa4, 0x76, 0xe7, 0xce, 0x1d, 0x32, 0x38, 0x38, 0xa8, 0xb8,
0xe6, 0xf5, 0x7a, 0x83, 0xb2, 0x05, 0xc8, 0x00, 0xc0, 0x24, 0xe4, 0xe3,
0xe8, 0x58, 0xe0, 0x11, 0x42, 0xb0, 0x79, 0xf3, 0xe6, 0x11, 0x9f, 0x33,
0x18, 0x0c, 0x60, 0x18, 0xe5, 0xf7, 0x97, 0xf2, 0xf2, 0xf2, 0xe2, 0xcb,
0x97, 0x2f, 0x77, 0xce, 0x9c, 0x39, 0x73, 0x36, 0x80, 0xa7, 0xaf, 0xdb,
0x1d, 0xc7, 0xe4, 0x02, 0x85, 0x85, 0x85, 0xd8, 0xb3, 0x67, 0x0f, 0xcc,
0x66, 0x33, 0xa2, 0xd1, 0xa8, 0xe2, 0x5e, 0x5b, 0x5b, 0x1b, 0xdc, 0x6e,
0x37, 0x56, 0xac, 0x58, 0x01, 0x00, 0x88, 0x44, 0x22, 0xdd, 0x4d, 0x4d,
0x4d, 0xa7, 0xf4, 0x7a, 0xbd, 0x01, 0xc0, 0x13, 0xd1, 0x55, 0xe4, 0xdc,
0xa8, 0xf5, 0x3c, 0x99, 0x3e, 0x7d, 0xfa, 0x3b, 0x79, 0x79, 0x79, 0x9f,
0x5f, 0xbc, 0x78, 0xf1, 0x2a, 0x85, 0xa0, 0x3e, 0x17, 0x98, 0x3b, 0x77,
0x2e, 0x89, 0x46, 0xa3, 0x84, 0x10, 0x42, 0xb6, 0x6f, 0xdf, 0x1e, 0xd3,
0x15, 0xa6, 0x4e, 0x9d, 0x2a, 0x99, 0x76, 0x47, 0x47, 0xc7, 0x4d, 0x00,
0xcb, 0x01, 0xe4, 0x01, 0x58, 0x08, 0x60, 0x3e, 0xfd, 0x3d, 0x5f, 0xd6,
0xe7, 0x01, 0x98, 0x0d, 0x20, 0x0b, 0x40, 0x36, 0x80, 0xc9, 0x00, 0xd2,
0x54, 0xe7, 0x02, 0xf3, 0xe6, 0xcd, 0x43, 0x38, 0x1c, 0x46, 0x46, 0xc6,
0xb3, 0x8c, 0xb5, 0x73, 0xe7, 0x4e, 0xe9, 0xb7, 0xbc, 0xf5, 0xf4, 0xf4,
0x60, 0xdf, 0xbe, 0x7d, 0x00, 0x80, 0x59, 0xb3, 0x66, 0x65, 0xed, 0xdd,
0xbb, 0xf7, 0x1b, 0x00, 0x7f, 0xd3, 0xe0, 0x36, 0x00, 0xe0, 0x3e, 0x80,
0x7e, 0x59, 0x1f, 0x90, 0xa5, 0xc2, 0x47, 0x00, 0x1e, 0xaa, 0x22, 0x08,
0xee, 0xd8, 0xb1, 0x43, 0x5a, 0x55, 0x93, 0xc9, 0x44, 0x1e, 0x3e, 0x7c,
0xf8, 0x42, 0x40, 0xdb, 0xbd, 0x7b, 0x77, 0x4c, 0x2b, 0xc8, 0xcc, 0xcc,
0x54, 0x3c, 0x37, 0x65, 0xca, 0x14, 0x07, 0x75, 0xb1, 0x4c, 0xaa, 0xa8,
0x36, 0x46, 0xcf, 0xa0, 0xe7, 0x07, 0x1a, 0x55, 0x04, 0x41, 0xd1, 0x57,
0x4b, 0x4b, 0x4b, 0x11, 0x0e, 0x87, 0xa1, 0xd3, 0xe9, 0x00, 0x00, 0xed,
0xed, 0xed, 0xf7, 0x6f, 0xdc, 0xb8, 0x31, 0x00, 0x00, 0x5b, 0xb6, 0x6c,
0x81, 0x5e, 0xaf, 0x7f, 0x01, 0x64, 0x7f, 0x7f, 0x3f, 0xea, 0xea, 0xea,
0xa4, 0x71, 0x43, 0x43, 0xc3, 0x77, 0xd4, 0xaf, 0x07, 0xa9, 0x25, 0x44,
0x63, 0xf4, 0x47, 0xf4, 0xde, 0x53, 0x55, 0x58, 0x40, 0x75, 0x75, 0x35,
0x99, 0x36, 0x6d, 0x9a, 0x62, 0x25, 0x7b, 0x7a, 0x7a, 0x06, 0x00, 0x54,
0x6d, 0xd8, 0xb0, 0xe1, 0x17, 0xf1, 0xda, 0xfe, 0xfd, 0xfb, 0x87, 0x4d,
0x8d, 0xb7, 0x6f, 0xdf, 0x96, 0xe6, 0x9a, 0x4c, 0xa6, 0x35, 0xb2, 0x34,
0x17, 0x57, 0x56, 0x79, 0xd3, 0x5d, 0x01, 0xc0, 0xe7, 0xf3, 0x91, 0xde,
0xde, 0x5e, 0x49, 0x81, 0xab, 0x57, 0xaf, 0xf6, 0x19, 0x8d, 0xc6, 0xf5,
0x00, 0x16, 0x68, 0xb5, 0xda, 0xca, 0x81, 0x81, 0x81, 0x7f, 0xc4, 0x7b,
0x46, 0xa3, 0x31, 0x26, 0x80, 0x6d, 0xdb, 0xb6, 0x49, 0xf3, 0xdb, 0xda,
0xda, 0xfe, 0xc4, 0xb3, 0x63, 0x73, 0x5d, 0x3c, 0x69, 0x2e, 0xe1, 0x00,
0xe4, 0xed, 0xd2, 0xa5, 0x4b, 0xf7, 0xb2, 0xb2, 0xb2, 0xd6, 0xd2, 0x88,
0xfd, 0x09, 0x00, 0xc3, 0xc6, 0x8d, 0x1b, 0x7f, 0x14, 0xef, 0xbb, 0xdd,
0xee, 0x61, 0x8b, 0xa3, 0xbb, 0x77, 0xef, 0x4a, 0x72, 0xac, 0x56, 0xeb,
0xd7, 0x54, 0x3f, 0x6d, 0xd2, 0x00, 0x38, 0x7f, 0xfe, 0xfc, 0x6d, 0x83,
0xc1, 0xf0, 0x25, 0x55, 0x3e, 0x17, 0xc0, 0xbb, 0xb4, 0xe7, 0x5e, 0xbb,
0x76, 0x4d, 0xb2, 0xf1, 0xdc, 0xdc, 0xdc, 0x98, 0x10, 0x6a, 0x6a, 0x6a,
0xe4, 0x56, 0xf0, 0x07, 0x75, 0x81, 0x09, 0x23, 0x59, 0x81, 0x2a, 0x00,
0x44, 0x22, 0x91, 0x5e, 0x9d, 0x4e, 0xb7, 0x92, 0x2a, 0x3e, 0x17, 0x80,
0x9e, 0x46, 0xf3, 0xb7, 0x01, 0xb0, 0x0e, 0x87, 0xe3, 0x5b, 0xf1, 0xd9,
0x53, 0xa7, 0x4e, 0x0d, 0x1b, 0x0b, 0x2e, 0x5c, 0xb8, 0x20, 0xc9, 0x74,
0x3a, 0x9d, 0x62, 0xb9, 0x38, 0x41, 0x75, 0x00, 0x6c, 0x36, 0xdb, 0x7a,
0xf1, 0x45, 0xc3, 0xe1, 0x70, 0xb7, 0x46, 0xa3, 0xa9, 0x06, 0xf0, 0x11,
0x80, 0x39, 0x54, 0x79, 0x8d, 0xac, 0x6a, 0x9c, 0x04, 0x20, 0xa7, 0xb3,
0xb3, 0xf3, 0xba, 0x38, 0xc7, 0x6e, 0xb7, 0xc7, 0x04, 0x60, 0xb1, 0x58,
0x24, 0x00, 0xdd, 0xdd, 0xdd, 0xbd, 0xb4, 0xe8, 0xd1, 0xca, 0xe4, 0xa9,
0x03, 0x40, 0x71, 0x71, 0xf1, 0x3a, 0x42, 0x08, 0x39, 0x7d, 0xfa, 0x74,
0x17, 0x80, 0x32, 0x5a, 0xa9, 0xcd, 0x91, 0xad, 0xbc, 0x58, 0xc7, 0x32,
0x34, 0xa7, 0x6b, 0xe4, 0x56, 0x10, 0x89, 0x44, 0x86, 0xb5, 0x82, 0xc3,
0x87, 0x0f, 0x4b, 0x10, 0x6a, 0x6a, 0x6a, 0xbe, 0x1f, 0xc9, 0x0a, 0x12,
0x02, 0x60, 0xd3, 0xa6, 0x4d, 0xf5, 0x57, 0xae, 0x5c, 0xe9, 0x05, 0xb0,
0x08, 0xc0, 0x87, 0x31, 0x56, 0x7e, 0xe8, 0xde, 0xc1, 0x00, 0xc0, 0x78,
0xee, 0xdc, 0xb9, 0x8b, 0xa2, 0x72, 0x16, 0x8b, 0x25, 0x26, 0x80, 0xad,
0x5b, 0xb7, 0x4a, 0x00, 0xee, 0xdd, 0xbb, 0xd7, 0xc7, 0x30, 0xcc, 0x4c,
0x5a, 0xf8, 0xb0, 0xaa, 0x01, 0x60, 0x32, 0x99, 0xcc, 0xd9, 0xd9, 0xd9,
0x66, 0x00, 0xef, 0x01, 0x98, 0x21, 0xae, 0xf2, 0x70, 0x47, 0x00, 0x78,
0xf6, 0x9f, 0xa4, 0xe9, 0x05, 0x05, 0x05, 0x5f, 0x89, 0xca, 0xb5, 0xb7,
0xb7, 0x2b, 0x14, 0x2f, 0x2f, 0x2f, 0x27, 0xa1, 0x50, 0xe8, 0x85, 0xcc,
0x72, 0xf0, 0xe0, 0xc1, 0x26, 0x2a, 0x27, 0x5d, 0x35, 0x00, 0xa8, 0x52,
0x19, 0x00, 0xde, 0xa7, 0xe6, 0xa9, 0x19, 0x21, 0x5b, 0x69, 0xe8, 0xe6,
0x25, 0xb3, 0xb1, 0xb1, 0xf1, 0x37, 0x51, 0xb9, 0xb5, 0x6b, 0xd7, 0x92,
0xbc, 0xbc, 0x3c, 0x72, 0xf2, 0xe4, 0xc9, 0x98, 0x67, 0x02, 0x27, 0x4e,
0x9c, 0xf8, 0x7d, 0xf5, 0xea, 0xd5, 0xdb, 0x00, 0xbc, 0x43, 0x63, 0x01,
0xa3, 0x16, 0x00, 0x62, 0x9d, 0x9e, 0x1e, 0xe7, 0x9e, 0x9c, 0x01, 0x30,
0x11, 0x80, 0x2e, 0x3b, 0x3b, 0xfb, 0x8b, 0x91, 0x0e, 0x43, 0xce, 0x9c,
0x39, 0xd3, 0x55, 0x51, 0x51, 0xf1, 0x03, 0x80, 0xcf, 0x00, 0x4c, 0x03,
0xf0, 0x81, 0xda, 0x00, 0x30, 0xb2, 0x1e, 0x6f, 0xd3, 0xd0, 0xba, 0x60,
0xc2, 0x81, 0x03, 0x07, 0xf8, 0x58, 0x8a, 0x77, 0x74, 0x74, 0xdc, 0x5a,
0xb7, 0x6e, 0xdd, 0x4f, 0x00, 0x9c, 0xb4, 0x9e, 0x58, 0x00, 0x20, 0xe7,
0x3f, 0xe2, 0x4b, 0xc2, 0x00, 0x8c, 0xea, 0x38, 0x50, 0x2c, 0x71, 0x8d,
0x46, 0x63, 0x95, 0x5c, 0xf1, 0x5b, 0xb7, 0x6e, 0x3d, 0xa8, 0xad, 0xad,
0xfd, 0x19, 0x40, 0x35, 0x80, 0x8f, 0xe9, 0x99, 0xc0, 0x2c, 0x0a, 0x6c,
0xe2, 0x90, 0xcc, 0x92, 0xb4, 0x00, 0x44, 0x2b, 0x30, 0x00, 0xd0, 0x1e,
0x3a, 0x74, 0xe8, 0xd7, 0xc1, 0xc1, 0x41, 0x52, 0x57, 0x57, 0xf7, 0xcb,
0xa4, 0x49, 0x93, 0xd6, 0xd0, 0x54, 0x9a, 0x4f, 0x0b, 0xa9, 0x2c, 0x0a,
0x2b, 0x6d, 0x24, 0x2b, 0x4b, 0x04, 0x00, 0x66, 0x0c, 0x00, 0xc4, 0xe0,
0xa9, 0xd5, 0xeb, 0xf5, 0xf3, 0x27, 0x4f, 0x9e, 0x3c, 0xa3, 0xab, 0xab,
0xeb, 0x2c, 0x55, 0xf4, 0x31, 0x3d, 0x10, 0x79, 0x40, 0xb7, 0xbc, 0x71,
0x6d, 0x77, 0xc7, 0xba, 0x22, 0xa3, 0x55, 0x62, 0xac, 0xdf, 0x15, 0x32,
0xa9, 0x9c, 0x74, 0x5a, 0x29, 0x8a, 0x27, 0x3e, 0x51, 0xd9, 0x79, 0x40,
0xdc, 0x87, 0xac, 0xc9, 0x06, 0x80, 0xa1, 0xae, 0x20, 0x76, 0x42, 0x95,
0x7e, 0xf2, 0x32, 0x8a, 0x27, 0x33, 0x80, 0x58, 0x32, 0x46, 0xad, 0x45,
0x42, 0x00, 0x24, 0xe2, 0x8f, 0xa6, 0xfc, 0xd7, 0xe1, 0x71, 0x00, 0xe3,
0x00, 0xc6, 0x01, 0x8c, 0x03, 0x18, 0x07, 0x30, 0x0e, 0x60, 0x1c, 0xc0,
0x38, 0x80, 0x64, 0x68, 0xff, 0x0e, 0x00, 0x22, 0x87, 0x86, 0xd2, 0x77,
0xfb, 0xb7, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82
};
#pragma clang diagnostic pop
@implementation FLEXResources
#define FLEXRetinaOnlyImage(base) ([self imageWithBytesNoCopy:(void *)(base) length:sizeof(base) scale:2.0])
#define FLEXImage(base) ( \
(UIScreen.mainScreen.scale > 1.5) ? \
( (UIScreen.mainScreen.scale > 2.5) ? \
[self imageWithBytesNoCopy:(void *)base##3x length:sizeof(base##3x) scale:3.0] : \
[self imageWithBytesNoCopy:(void *)base##2x length:sizeof(base##2x) scale:2.0] \
) : \
nil \
[self imageWithBytesNoCopy:(void *)base##2x length:sizeof(base##2x) scale:2.0] \
)
#define FLEXImageTemplate(base) ([FLEXImage(base) imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate])
#define FLEXRetinaOnlyImage(base) ([self imageWithBytesNoCopy:(void *)(base) length:sizeof(base) scale:2.0])
#pragma mark - FLEX Toolbar Icons
+ (UIImage *)closeIcon {
@@ -8846,6 +9235,9 @@ static const u_int8_t FLEXHierarchyIndentPattern3x[] = {
return FLEXImageTemplate(FLEXHierarchyIndentPattern);
}
+ (UIImage *)cursorImage {
return FLEXRetinaOnlyImage(FLEXCursor);
}
#undef FLEXImage
#undef FLEXRetinaOnlyImage
+6
View File
@@ -46,7 +46,13 @@
+ (NSString *)pointerToString:(void *)ptr;
+ (NSString *)addressOfObject:(id)object;
+ (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString;
#if !TARGET_OS_TV
+ (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask;
#else
+ (BOOL)airdropAvailable;
+ (void)airDropFile:(NSString *)file;
+ (NSUInteger)infoPlistSupportedInterfaceOrientationsMask;
#endif
+ (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data;
+ (NSString *)stringFromRequestDuration:(NSTimeInterval)duration;
+ (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response;
+20 -1
View File
@@ -255,9 +255,14 @@ BOOL FLEXConstructorsShouldRun() {
return [mutableString copy];
}
#if !TARGET_OS_TV
+ (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask {
#else
+ (NSUInteger)infoPlistSupportedInterfaceOrientationsMask {
return 0;
#endif
NSArray<NSString *> *supportedOrientations = NSBundle.mainBundle.infoDictionary[@"UISupportedInterfaceOrientations"];
#if !TARGET_OS_TV
UIInterfaceOrientationMask supportedOrientationsMask = 0;
if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) {
supportedOrientationsMask |= UIInterfaceOrientationMaskPortrait;
@@ -272,6 +277,7 @@ BOOL FLEXConstructorsShouldRun() {
supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeLeft;
}
return supportedOrientationsMask;
#endif
}
+ (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data {
@@ -529,4 +535,17 @@ BOOL FLEXConstructorsShouldRun() {
}
}
#if TARGET_OS_TV
+ (BOOL)airdropAvailable {
return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"airdropper://"]];
}
+ (void)airDropFile:(NSString *)file {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"airdropper://%@", file]];
UIApplication *application = [UIApplication sharedApplication];
[application openURL:url options:@{} completionHandler:nil];
}
#endif
@end
@@ -8,10 +8,14 @@
#import "FLEXKeyboardHelpViewController.h"
#import "FLEXKeyboardShortcutManager.h"
#import "KBSelectableTextView.h"
@interface FLEXKeyboardHelpViewController ()
#if TARGET_OS_TV
@property (nonatomic) KBSelectableTextView *textView;
#else
@property (nonatomic) UITextView *textView;
#endif
@end
@@ -19,8 +23,11 @@
- (void)viewDidLoad {
[super viewDidLoad];
#if TARGET_OS_TV
self.textView = [[KBSelectableTextView alloc] initWithFrame:self.view.bounds];
#else
self.textView = [[UITextView alloc] initWithFrame:self.view.bounds];
#endif
self.textView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.textView];
#if TARGET_OS_SIMULATOR
@@ -29,8 +36,9 @@
self.textView.backgroundColor = UIColor.blackColor;
self.textView.textColor = UIColor.whiteColor;
self.textView.font = [UIFont boldSystemFontOfSize:14.0];
#if !TARGET_OS_TV
self.navigationController.navigationBar.barStyle = UIBarStyleBlackOpaque;
#endif
self.title = @"Simulator Shortcuts";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(donePressed:)];
}
@@ -7,6 +7,7 @@
//
#import "FLEXNavigationController.h"
#import <TargetConditionals.h>
@protocol FLEXHierarchyDelegate <NSObject>
- (void)viewHierarchyDidDismiss:(UIView *)selectedView;
@@ -14,6 +14,7 @@
#import "FLEXResources.h"
#import "UIBarButtonItem+FLEX.h"
typedef NS_ENUM(NSUInteger, FLEXHierarchyViewMode) {
FLEXHierarchyViewModeTree = 1,
FLEXHierarchyViewMode3DSnapshot
@@ -69,12 +70,12 @@ typedef NS_ENUM(NSUInteger, FLEXHierarchyViewMode) {
- (void)viewDidLoad {
[super viewDidLoad];
#if !TARGET_OS_TV
// 3D toggle button
self.treeViewController.navigationItem.leftBarButtonItem = [UIBarButtonItem
flex_itemWithImage:FLEXResources.toggle3DIcon target:self action:@selector(toggleHierarchyMode)
];
#endif
// Dismiss when tree view row is selected
__weak id<FLEXHierarchyDelegate> delegate = self.hierarchyDelegate;
self.treeViewController.didSelectRowAction = ^(UIView *selectedView) {
@@ -127,16 +128,20 @@ typedef NS_ENUM(NSUInteger, FLEXHierarchyViewMode) {
switch (mode) {
case FLEXHierarchyViewModeTree:
[self popViewControllerAnimated:NO];
#if !TARGET_OS_TV
self.toolbarHidden = YES;
#endif
self.treeViewController.selectedView = self.selectedView;
break;
case FLEXHierarchyViewMode3DSnapshot:
[self pushViewController:self.snapshotViewController animated:NO];
#if !TARGET_OS_TV
self.toolbarHidden = NO;
#endif
self.snapshotViewController.selectedView = self.selectedView;
break;
}
// Change this last so that self.selectedView works right above
_mode = mode;
}
@@ -26,6 +26,9 @@
#pragma mark Initialization
+ (instancetype)previewForView:(UIView *)view {
#if TARGET_OS_TV
return [self forImage:[FLEXUtility previewImageForLayer:view.layer]]; //for some reason the one view 'view' below literally never works on tvOS
#endif
return [self forImage:[FLEXUtility previewImageForView:view]];
}
@@ -113,7 +116,30 @@
self.scrollView.backgroundColor = self.backgroundColors[self.backgroundColorIndex];
}
+ (NSString *)documentsFolder {
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
- (void)actionButtonPressed:(id)sender {
#if TARGET_OS_TV
if ([FLEXUtility airdropAvailable]){
NSString *outputFile = [[FLEXImagePreviewViewController documentsFolder] stringByAppendingPathComponent:@"FLEXViewImage.png"];
FXLog(@"exporting view image to file: %@", outputFile);
NSFileManager *man = [NSFileManager defaultManager];
if ([man fileExistsAtPath:outputFile]){
[man removeItemAtPath:outputFile error:nil];
}
[UIImagePNGRepresentation(self.image) writeToFile:outputFile atomically:true];
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"airdropper://%@?sender=%@", outputFile, bundleID]];
[[UIApplication sharedApplication] openURL:url];
return;
} else {
[FLEXAlert showAlert:@"Oh no" message:@"A jailbroken AppleTV is required to share files through AirDrop, sorry!" from:self];
}
#endif
static BOOL canSaveToCameraRoll = NO, didShowWarning = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -127,7 +153,7 @@
canSaveToCameraRoll = YES;
}
});
#if !TARGET_OS_TV
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[self.image] applicationActivities:@[]];
if (!canSaveToCameraRoll && !didShowWarning) {
@@ -142,6 +168,7 @@
} else {
[self presentViewController:activityVC animated:YES completion:nil];
}
#endif
}
@end
@@ -8,7 +8,7 @@
#import "FHSViewSnapshot.h"
#import "FHSRangeSlider.h"
#import "fakes.h"
NS_ASSUME_NONNULL_BEGIN
@protocol FHSSnapshotViewDelegate <NSObject>
@@ -30,9 +30,12 @@ NS_ASSUME_NONNULL_BEGIN
/// Views of these classes will have their headers hidden
@property (nonatomic) NSArray<Class> *headerExclusions;
@property (nonatomic, readonly) FHSRangeSlider *depthSlider; //this is a UIControl, it wont work OOB on tvOS but the snapshot viewer isnt working on tvOS anyway, moot for now.
#if !TARGET_OS_TV
@property (nonatomic, readonly) UISlider *spacingSlider;
@property (nonatomic, readonly) FHSRangeSlider *depthSlider;
#else
@property (nonatomic, readonly) KBSlider *spacingSlider;
#endif
- (void)emphasizeViews:(NSArray<UIView *> *)emphasizedViews;
@@ -66,7 +66,11 @@
}
- (void)initSpacingSlider {
#if !TARGET_OS_TV
_spacingSlider = [UISlider new];
#else
_spacingSlider = [KBSlider new];
#endif
self.spacingSlider.minimumValue = 0;
self.spacingSlider.maximumValue = 100;
self.spacingSlider.continuous = YES;
@@ -273,7 +277,7 @@
}
}
- (void)spacingSliderDidChange:(UISlider *)slider {
- (void)spacingSliderDidChange:(KBSlider *)slider { //easier to make a KBSlider since they are API compatible - one less #if macro!
// TODO: hiding the header when flat logic
for (FHSSnapshotNodes *nodes in self.nodesMap.allValues) {
@@ -87,9 +87,16 @@
@implementation FHSView (Snapshotting)
+ (UIImage *)drawView:(UIView *)view {
#if TARGET_OS_TV
UIGraphicsBeginImageContextWithOptions(view.layer.bounds.size, NO, 0.0);
CGContextRef imageContext = UIGraphicsGetCurrentContext();
[view.layer renderInContext:imageContext];
#else
UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
#endif
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
@@ -118,7 +118,9 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
[super viewDidLoad];
// Initialize back bar button item for 3D view to look like a button
#if !TARGET_OS_TV
self.navigationItem.hidesBackButton = YES;
#endif
self.navigationItem.leftBarButtonItem = [UIBarButtonItem
flex_itemWithImage:FLEXResources.toggle2DIcon
target:self.navigationController
@@ -155,7 +157,7 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
NSParameterAssert(snapshotView);
_snapshotView = snapshotView;
#if !TARGET_OS_TV
// Initialize our toolbar items
self.toolbarItems = @[
[UIBarButtonItem flex_itemWithCustomView:snapshotView.spacingSlider],
@@ -168,7 +170,7 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
[UIBarButtonItem flex_itemWithCustomView:snapshotView.depthSlider]
];
[self resizeToolbarItems:self.view.frame.size];
#endif
// If we have views-at-tap, dim the other views
[snapshotView emphasizeViews:self.viewsAtTap];
// Set the selected view, if any
@@ -230,7 +232,9 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
} showFrom:self source:sender];
}
- (void)resizeToolbarItems:(CGSize)viewSize {
#if !TARGET_OS_TV
CGFloat sliderHeights = self.snapshotView.spacingSlider.bounds.size.height;
CGFloat sliderWidths = viewSize.width / 3.f;
CGRect frame = CGRectMake(0, 0, sliderWidths, sliderHeights);
@@ -238,6 +242,7 @@ BOOL const kFHSViewControllerExcludeFLEXWindows = YES;
self.snapshotView.depthSlider.frame = frame;
[self.navigationController.toolbar setNeedsLayout];
#endif
}
- (void)viewWillTransitionToSize:(CGSize)size
@@ -43,9 +43,13 @@
self.textLabel.font = UIFont.flex_defaultTableCellFont;
self.detailTextLabel.font = UIFont.flex_defaultTableCellFont;
#if !TARGET_OS_TV
self.accessoryType = UITableViewCellAccessoryDetailButton;
#else
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
#endif
// Use a pattern-based color to simplify application of the checker pattern
static UIColor *checkerPatternColor = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
@@ -63,6 +63,27 @@ typedef NS_ENUM(NSUInteger, FLEXHierarchyScope) {
return self;
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded) {
NSLog(@"do something different for long press!");
UITableView *tv = [self tableView];
//naughty naughty
NSIndexPath *focus = [tv valueForKey:@"_focusedCellIndexPath"];
NSLog(@"[FLEX] focusedIndexPath: %@", focus);
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:focus];
}
}
- (void)addlongPressGestureRecognizer {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
longPress.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeSelect]];
[self.tableView addGestureRecognizer:longPress];
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
rightTap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeRightArrow]];
[self.tableView addGestureRecognizer:rightTap];
}
- (void)viewDidLoad {
[super viewDidLoad];
@@ -71,7 +92,12 @@ typedef NS_ENUM(NSUInteger, FLEXHierarchyScope) {
// A little more breathing room
self.tableView.rowHeight = 50.0;
#if !TARGET_OS_TV
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
#else
[self addlongPressGestureRecognizer];
self.tableView.rowHeight = 70.0;
#endif
// Separator inset clashes with persistent cell selection
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
@@ -230,8 +256,10 @@ typedef NS_ENUM(NSUInteger, FLEXHierarchyScope) {
cell.textLabel.textColor = FLEXColor.deemphasizedTextColor;
cell.detailTextLabel.textColor = FLEXColor.deemphasizedTextColor;
} else {
#if !TARGET_OS_TV
cell.textLabel.textColor = FLEXColor.primaryTextColor;
cell.detailTextLabel.textColor = FLEXColor.primaryTextColor;
#endif
}
return cell;
@@ -0,0 +1,5 @@
#import <UIKit/UIKit.h>
@interface FLEXFontListTableViewController : UITableViewController
@property (nonatomic, copy, nullable) void (^itemSelectedBlock)(NSString * _Nullable fontName);
@end
@@ -0,0 +1,99 @@
#import "FLEXFontListTableViewController.h"
#import "NSObject+FLEX_Reflection.h"
@interface FLEXFontListTableViewController ()
@property (nonatomic) NSArray *fonts;
@end
@implementation FLEXFontListTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createAvailableFonts];
//self.fonts = [self allFonts];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"reuseID"];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewWillAppear:(BOOL)animated {
if ([self darkMode]){
self.view.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
} else {
self.view.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (void)createAvailableFonts {
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray new];
for (NSString *eachFontFamily in UIFont.familyNames) {
for (NSString *eachFontName in [UIFont fontNamesForFamilyName:eachFontFamily]) {
[unsortedFontsArray addObject:eachFontName];
}
}
self.fonts = [NSMutableArray arrayWithArray:[unsortedFontsArray sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]];
}
- (NSArray *)allFonts
{
NSMutableArray *allFontArray = [[NSMutableArray alloc] init];
NSArray *fontNames = [UIFont familyNames]; //all font family names
for (NSString *fontFamily in fontNames) //cycle through
{
[allFontArray addObjectsFromArray:[UIFont fontNamesForFamilyName:fontFamily]]; //add all font names from the family names to the array
}
NSArray *sortedArray = [allFontArray sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; //sort alphabetically
return sortedArray;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self fonts].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"reuseID" forIndexPath:indexPath];
NSString *currentFont = [self fonts][indexPath.row];
cell.textLabel.text = currentFont;
cell.textLabel.font = [UIFont fontWithName:currentFont size:45];
// Configure the cell...
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *currentFont = [self fonts][indexPath.row];
if (self.itemSelectedBlock){
self.itemSelectedBlock(currentFont);
}
}
@end
+53
View File
@@ -0,0 +1,53 @@
#import <UIKit/UIKit.h>
#import "Macros.h"
#define NUMBER_OF_CELLS 100000
#define TABLE_TAG(XX) \
XX(KBTableViewTagMonths, = 501) \
XX(KBTableViewTagDays, )\
XX(KBTableViewTagYears, )\
XX(KBTableViewTagHours, )\
XX(KBTableViewTagMinutes, )\
XX(KBTableViewTagAMPM, )\
XX(KBTaleViewWeekday, )
DECLARE_ENUM(KBTableViewTag, TABLE_TAG)
#define PICKER_MODE(XX) \
XX(KBDatePickerModeTime, ) \
XX(KBDatePickerModeDate, ) \
XX(KBDatePickerModeDateAndTime, ) \
XX(KBDatePickerModeCountDownTimer, )
DECLARE_ENUM(KBDatePickerMode, PICKER_MODE)
@interface UIView (Helper)
-(void)removeAllSubviews;
@end
@interface UIStackView (Helper)
- (void)removeAllArrangedSubviews;
- (void)setArrangedViews:(NSArray * _Nonnull )views;
@end
@interface KBTableView: UITableView
@property NSIndexPath * _Nullable selectedIndexPath;
@property CGFloat customWidth;
@property id _Nullable selectedValue;
- (instancetype _Nonnull )initWithTag:(KBTableViewTag)tag delegate:(id _Nonnull )delegate;
- (id _Nullable )valueForIndexPath:(NSIndexPath *_Nonnull)indexPath;
- (NSArray *_Nonnull)visibleValues;
@end
// Enums are all defined like this to make it easier to convert them to / from string versions of themselves.
@interface KBDatePickerView: UIControl <UITableViewDelegate, UITableViewDataSource>
@property (nonnull, nonatomic, strong) NSDate *date;
@property (nullable, nonatomic, strong) NSDate *minimumDate;
@property (nullable, nonatomic, strong) NSDate *maximumDate;
@property BOOL showDateLabel;
@property KBDatePickerMode datePickerMode;
@property NSInteger topOffset;
@property (nonatomic, copy, nullable) void (^itemSelectedBlock)(NSDate * _Nullable date);
+(id _Nonnull )todayInYear:(NSInteger)year;
+ (NSDateFormatter * _Nonnull )sharedDateFormatter;
@end
#define DPLog(format, ...) NSLog(@"[KBDatePickerView] %@",[NSString stringWithFormat:format, ## __VA_ARGS__]);
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
@interface KBSelectableTextView : UITextView <UITextFieldDelegate>
- (id)initForAutoLayout;
@property (nonatomic, weak) UIViewController *parentView;
@property (readwrite, assign) BOOL focusColorChange;
@property (nonatomic, strong) UIViewController *inputViewController;
@end
+128
View File
@@ -0,0 +1,128 @@
#import "KBSelectableTextView.h"
#import "NSObject+FLEX_Reflection.h"
@interface KBSelectableTextView(){
NSString *_startValue;
NSString *_endValue;
}
@property UITextField *_backingTextField; //i absolutely hate this but i cant figure out how to get a keyboard editing view to present from a UITextView manually.
@end
@implementation KBSelectableTextView
- (BOOL)becomeFirstResponder {
[super becomeFirstResponder];
return [__backingTextField becomeFirstResponder];
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
_startValue = textField.text;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
_endValue = textField.text;
if (_startValue != _endValue && _endValue.length > 0){
[self setText:textField.text];
}
}
- (void)_sharedInitialize {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
tap.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypeSelect]];
[self addGestureRecognizer:tap];
self.selectable = YES;
self.userInteractionEnabled = YES;
self.scrollEnabled = NO;
self.layoutManager.allowsNonContiguousLayout = NO;
self.panGestureRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirect)];
self.focusColorChange = YES;
__backingTextField = [[UITextField alloc] initWithFrame:CGRectZero];
[self addSubview:__backingTextField];
__backingTextField.delegate = self;
__backingTextField.text = self.text;
__backingTextField.placeholder = self.text;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
[self _sharedInitialize];
return self;
}
- (id)initForAutoLayout
{
self = [super init];
self.translatesAutoresizingMaskIntoConstraints = NO;
[self _sharedInitialize];
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (BOOL)isSelectable
{
return YES;
}
- (BOOL)canBecomeFocused
{
return YES;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
if (self.focusColorChange == NO) {
[super didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
return;
}
if (context.nextFocusedView == self)
{
[coordinator addCoordinatedAnimations:^{
UIColor *whiteAlpha = [UIColor colorWithWhite:1 alpha:.3];
self.layer.backgroundColor = whiteAlpha.CGColor;
self.layer.shadowOffset = CGSizeMake(5.0, 5.0);
self.layer.shadowRadius = 4;
self.layer.shadowOpacity = 1.0;
self.layer.shadowColor = [UIColor blackColor].CGColor;
} completion:^{
}];
} else {
[coordinator addCoordinatedAnimations:^{
self.layer.backgroundColor = [UIColor clearColor].CGColor;
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowRadius = 0;
self.layer.shadowOpacity = 0;
self.layer.shadowColor = [UIColor clearColor].CGColor;
} completion:^{
}];
}
}
- (void)tap {
NSLog(@"[FLEXLog] tapped");
if(self.inputViewController == nil){
if (self.inputView){
NSLog(@"[FLEXLog] unhandled input view type: %@", self.inputView);
}
[self becomeFirstResponder];
} else {
[[self topViewController] presentViewController:self.inputViewController animated:true completion:nil];
}
}
@end
+49
View File
@@ -0,0 +1,49 @@
//
// KBSlider.h
// KBSlider
//
// Created by Kevin Bradley on 12/25/20.
// Copyright © 2020 nito. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, DPadState) {
DPadStateSelect,
DPadStateRight,
DPadStateLeft,
DPadStateUp,
DPadStateDown,
};
@interface KBSlider : UIControl
@property CGFloat value;
@property CGFloat minimumValue;
@property CGFloat maximumValue;
@property(nonatomic,getter=isContinuous) BOOL continuous;
@property UIColor *maximumTrackTintColor;
@property UIColor *minimumTrackTintColor;
@property UIColor *thumbTintColor;
@property CGFloat focusScaleFactor;
@property CGFloat stepValue;
@property UIImage *currentMinimumTrackImage;
@property UIImage *currentMaximumTrackImage;
@property CGFloat storedValue;
- (UIImage *)currentThumbImage;
- (void)setValue:(CGFloat)value animated:(BOOL)animated;
- (void)setMinimumTrackImage:(UIImage *)minTrackImage forState:(UIControlState)state;
- (void)setMaximumTrackImage:(UIImage *)maxTrackImage forState:(UIControlState)state;
- (void)setThumbImage:(UIImage *)thumbImage forState:(UIControlState)state;
- (UIImage *)minimumTrackImageForState:(UIControlState)state;
- (UIImage *)maximumTrackImageForState:(UIControlState)state;
- (UIImage *)thumbImageForState:(UIControlState)state;
@end
NS_ASSUME_NONNULL_END
+557
View File
@@ -0,0 +1,557 @@
//
// KBSlider.m
// KBSlider
//
// Created by Kevin Bradley on 12/25/20.
// Copyright © 2020 nito. All rights reserved.
//
#import "KBSlider.h"
#import <GameController/GameController.h>
@interface KBSlider() {
CGFloat _minimumValue;
CGFloat _maximumValue;
UIColor *_maximumTrackTintColor;
UIColor *_minimumTrackTintColor;
UIColor *_thumbTintColor;
CGFloat _focusScaleFactor;
BOOL _continuous;
BOOL _isEnabled;
BOOL _isSelected;
BOOL _isHighlighted;
}
@property CGFloat trackViewHeight;
@property CGFloat thumbSize;
@property NSTimeInterval animationDuration;
@property CGFloat defaultValue;
@property CGFloat defaultMinimumValue;
@property CGFloat defaultMaximumValue;
@property BOOL defaultIsContinuous;
@property UIColor *defaultThumbTintColor;
@property UIColor *defaultTrackColor;
@property UIColor *defaultMininumTrackTintColor;
@property CGFloat defaultFocusScaleFactor;
@property CGFloat defaultStepValue;
@property CGFloat decelerationRate;
@property CGFloat decelerationMaxVelocity;
@property CGFloat fineTunningVelocityThreshold;
@property NSMutableDictionary *thumbViewImages; //[UInt: UIImage] - not an allowed dict type in obj-c
@property UIImageView *thumbView;
@property NSMutableDictionary *trackViewImages; //[UInt: UIImage] - not an allowed dict type in obj-c
@property UIImageView *trackView;
@property NSMutableDictionary *minimumTrackViewImages; //[UInt: UIImage] - not an allowed dict type in obj-c
@property UIImageView *minimumTrackView;
@property NSMutableDictionary *maximumTrackViewImages; //[UInt: UIImage] - not an allowed dict type in obj-c
@property UIImageView *maximumTrackView;
@property UIPanGestureRecognizer *panGestureRecognizer;
@property UITapGestureRecognizer *leftTapGestureRecognizer;
@property UITapGestureRecognizer *rightTapGestureRecognizer;
@property NSLayoutConstraint *thumbViewCenterXConstraint;
@property DPadState dPadState; //.select
@property NSTimer *deceleratingTimer;
@property CGFloat deceleratingVelocity;
@property CGFloat thumbViewCenterXConstraintConstant;
@end
@implementation KBSlider
- (void)initializeDefaults {
_trackViewHeight = 5;
_thumbSize = 30;
_animationDuration = 0.3;
_defaultValue = 0;
_defaultMinimumValue = 0;
_defaultMaximumValue = 1;
_defaultIsContinuous = true;
_defaultThumbTintColor = [UIColor whiteColor];
_defaultTrackColor = [UIColor grayColor];
_defaultMininumTrackTintColor = [UIColor blueColor];
_defaultFocusScaleFactor = 1.05;
_defaultStepValue = 0.1;
_decelerationRate = 0.92;
_decelerationMaxVelocity = 1000;
_fineTunningVelocityThreshold = 600;
_storedValue = _defaultValue;
_dPadState = DPadStateSelect;
_continuous = _defaultIsContinuous;
_minimumTrackViewImages = [NSMutableDictionary new];
_maximumTrackViewImages = [NSMutableDictionary new];
_trackViewImages = [NSMutableDictionary new];
_thumbViewImages = [NSMutableDictionary new];
_thumbTintColor = _defaultThumbTintColor;
_minimumTrackTintColor = _defaultMininumTrackTintColor;
_focusScaleFactor = _defaultFocusScaleFactor;
_minimumValue = _defaultMinimumValue;
_maximumValue = _defaultMaximumValue;
_stepValue = _defaultStepValue;
[self setEnabled:true];
}
- (BOOL)isContinuous {
return _continuous;
}
- (void)setContinuous:(BOOL)continuous {
_continuous = continuous;
}
- (void)setSelected:(BOOL)selected {
_isSelected = selected;
[self updateStateDependantViews];
}
- (BOOL)isSelected {
return _isSelected;
}
- (void)setHighlighted:(BOOL)highlighted {
_isHighlighted = highlighted;
[self updateStateDependantViews];
}
- (BOOL)isHighlighted {
return _isHighlighted;
}
- (void)setEnabled:(BOOL)enabled {
_isEnabled = enabled;
_panGestureRecognizer.enabled = enabled;
[self updateStateDependantViews];
}
- (BOOL)isEnabled {
return _isEnabled;
}
- (CGFloat)value {
return _storedValue;
}
- (void)setValue:(CGFloat)value afterDelay:(NSInteger)delay {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setValue:value];
});
}
- (void)setValue:(CGFloat)newValue {
_storedValue = MIN(_maximumValue, newValue);
_storedValue = MAX(_minimumValue, _storedValue);
if (_trackView.bounds.size.width == 0){
[self setValue:newValue afterDelay:0.1];
}
CGFloat offset = _trackView.bounds.size.width * (_storedValue - _minimumValue) / (_maximumValue - _minimumValue);
offset = MIN(_trackView.bounds.size.width, offset);
if(isnan(offset)){
return;
}
_thumbViewCenterXConstraint.constant = offset;
}
- (CGFloat)maximumValue {
return _maximumValue;
}
- (void)setMaximumValue:(CGFloat)maximumValue {
_maximumValue = maximumValue;
[self setValue:MIN(self.value, maximumValue)];
}
- (CGFloat)minimumValue {
return _minimumValue;
}
- (void)setMinimumValue:(CGFloat)minimumValue {
_minimumValue = minimumValue;
[self setValue:MAX(self.value, minimumValue)];
}
- (UIColor *)maximumTrackTintColor {
return _maximumTrackTintColor;
}
- (void)setMaximumTrackTintColor:(UIColor *)maximumTrackTintColor {
_maximumTrackTintColor = maximumTrackTintColor;
_maximumTrackView.backgroundColor = maximumTrackTintColor;
}
- (void)setMinimumTrackTintColor:(UIColor *)minimumTrackTintColor {
_minimumTrackTintColor = minimumTrackTintColor;
_minimumTrackView.backgroundColor = minimumTrackTintColor;
}
- (UIColor *)thumbTintColor {
return _thumbTintColor;
}
- (void)setThumbTintColor:(UIColor *)thumbTintColor {
_thumbTintColor = thumbTintColor;
_thumbView.backgroundColor = thumbTintColor;
}
- (UIColor *)minimumTrackTintColor {
return _minimumTrackTintColor;
}
- (CGFloat)focusScaleFactor {
return _focusScaleFactor;
}
- (void)setFocusScaleFactor:(CGFloat)focusScaleFactor {
_focusScaleFactor = focusScaleFactor;
[self updateStateDependantViews];
}
- (void)setupView {
[self initializeDefaults];
[self setUpTrackView];
[self setUpMinimumTrackView];
[self setUpMaximumTrackView];
[self setUpThumbView];
[self setUpTrackViewConstraints];
[self setUpMinimumTrackViewConstraints];
[self setUpMaximumTrackViewConstraints];
[self setUpThumbViewConstraints];
[self setUpGestures];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerConnected:) name:GCControllerDidConnectNotification object:nil];
[self updateStateDependantViews];
}
- (void)setValue:(CGFloat)value animated:(BOOL)animated {
[self setValue:value];
[self stopDeceleratingTimer];
if (animated){
[UIView animateWithDuration:self.animationDuration animations:^{
[self setNeedsLayout];
[self layoutIfNeeded];
}];
}
}
- (void)setMinimumTrackImage:(UIImage *)image forState:(UIControlState)state {
_minimumTrackViewImages[[NSNumber numberWithUnsignedInteger:state]] = image;
[self updateStateDependantViews];
}
- (void)setMaximumTrackImage:(UIImage *)image forState:(UIControlState)state {
_maximumTrackViewImages[[NSNumber numberWithUnsignedInteger:state]] = image;
[self updateStateDependantViews];
}
- (void)setThumbImage:(UIImage *)image forState:(UIControlState)state {
_thumbViewImages[[NSNumber numberWithUnsignedInteger:state]] = image;
[self updateStateDependantViews];
}
- (UIImage *)currentThumbImage {
return _thumbView.image;
}
- (UIImage *)minimumTrackImageForState:(UIControlState)state {
NSNumber *key = [NSNumber numberWithUnsignedInteger:state];
return _minimumTrackViewImages[key];
}
- (UIImage *)maximumTrackImageForState:(UIControlState)state {
NSNumber *key = [NSNumber numberWithUnsignedInteger:state];
return _maximumTrackViewImages[key];
}
- (UIImage *)thumbImageForState:(UIControlState)state {
NSNumber *key = [NSNumber numberWithUnsignedInteger:state];
return _thumbViewImages[key];
}
- (void)setUpThumbView {
_thumbView = [UIImageView new];
_thumbView.layer.cornerRadius = _thumbSize/2;
_thumbView.backgroundColor = _thumbTintColor;
[self addSubview:_thumbView];
}
- (void)setUpTrackView {
_trackView = [UIImageView new];
_trackView.layer.cornerRadius = _trackViewHeight/2;
_trackView.backgroundColor = _defaultTrackColor;
[self addSubview:_trackView];
}
- (void)setUpMinimumTrackView {
_minimumTrackView = [UIImageView new];
_minimumTrackView.layer.cornerRadius = _trackViewHeight/2;
_minimumTrackView.backgroundColor = _minimumTrackTintColor;
[self addSubview:_minimumTrackView];
}
- (void)setUpMaximumTrackView {
_maximumTrackView = [UIImageView new];
_maximumTrackView.layer.cornerRadius = _trackViewHeight/2;
_maximumTrackView.backgroundColor = _maximumTrackTintColor;
[self addSubview:_maximumTrackView];
}
- (void)setUpTrackViewConstraints {
_trackView.translatesAutoresizingMaskIntoConstraints = false;
[_trackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = true;
[_trackView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = true;
[_trackView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor].active = true;
[_trackView.heightAnchor constraintEqualToConstant:_trackViewHeight].active = true;
}
- (void)setUpMinimumTrackViewConstraints {
_minimumTrackView.translatesAutoresizingMaskIntoConstraints = false;
[_minimumTrackView.leadingAnchor constraintEqualToAnchor:_trackView.leadingAnchor].active = true;
[_minimumTrackView.trailingAnchor constraintEqualToAnchor:_thumbView.centerXAnchor].active = true;
[_minimumTrackView.centerYAnchor constraintEqualToAnchor:_trackView.centerYAnchor].active = true;
[_minimumTrackView.heightAnchor constraintEqualToConstant:_trackViewHeight].active = true;
}
- (void)setUpMaximumTrackViewConstraints {
_maximumTrackView.translatesAutoresizingMaskIntoConstraints = false;
[_maximumTrackView.leadingAnchor constraintEqualToAnchor:_thumbView.centerXAnchor].active = true;
[_maximumTrackView.trailingAnchor constraintEqualToAnchor:_trackView.trailingAnchor].active = true;
[_maximumTrackView.centerYAnchor constraintEqualToAnchor:_trackView.centerYAnchor].active = true;
[_maximumTrackView.heightAnchor constraintEqualToConstant:_trackViewHeight].active = true;
}
- (void)setUpThumbViewConstraints {
_thumbView.translatesAutoresizingMaskIntoConstraints = false;
[_thumbView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor].active = true;
[_thumbView.heightAnchor constraintEqualToConstant:_thumbSize].active = true;
[_thumbView.widthAnchor constraintEqualToConstant:_thumbSize].active = true;
_thumbViewCenterXConstraint = [_thumbView.centerXAnchor constraintEqualToAnchor:_trackView.leadingAnchor constant:self.value];
_thumbViewCenterXConstraint.active = true;
}
- (void)setUpGestures {
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureWasTriggered:)];
[self addGestureRecognizer:_panGestureRecognizer];
_leftTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(leftTapWasTriggered)];
_leftTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeLeftArrow)];
_leftTapGestureRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirect)];
[self addGestureRecognizer:_leftTapGestureRecognizer];
_rightTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(rightTapWasTriggered)];
_rightTapGestureRecognizer.allowedPressTypes = @[@(UIPressTypeRightArrow)];
_rightTapGestureRecognizer.allowedTouchTypes = @[@(UITouchTypeIndirect)];
[self addGestureRecognizer:_rightTapGestureRecognizer];
}
- (void)updateStateDependantViews {
UIImage *currentMinImage = _minimumTrackViewImages[[NSNumber numberWithUnsignedInteger:self.state]];
if (currentMinImage){
_minimumTrackView.image = currentMinImage;
} else {
_minimumTrackView.image = _minimumTrackViewImages[[NSNumber numberWithUnsignedInteger:UIControlStateNormal]];
}
UIImage *currentMaxImage = _maximumTrackViewImages[[NSNumber numberWithUnsignedInteger:self.state]];
if (currentMaxImage){
_maximumTrackView.image = currentMaxImage;
} else {
_maximumTrackView.image = _maximumTrackViewImages[[NSNumber numberWithUnsignedInteger:UIControlStateNormal]];
}
UIImage *currentThumbImage = _thumbViewImages[[NSNumber numberWithUnsignedInteger:self.state]];
if (currentThumbImage){
_thumbView.image = currentThumbImage;
} else {
_thumbView.image = _thumbViewImages[[NSNumber numberWithUnsignedInteger:UIControlStateNormal]];
}
if ([self isFocused]){
self.transform = CGAffineTransformMakeScale(_focusScaleFactor, _focusScaleFactor);
} else {
self.transform = CGAffineTransformIdentity;
}
}
- (void)controllerConnected:(NSNotification *)n {
GCController *controller = [n object];
GCMicroGamepad *micro = [controller microGamepad];
if (!micro)return;
CGFloat threshold = 0.7;
micro.reportsAbsoluteDpadValues = true;
micro.dpad.valueChangedHandler = ^(GCControllerDirectionPad * _Nonnull dpad, float xValue, float yValue) {
if (xValue < -threshold){
self.dPadState = DPadStateLeft;
} else if (xValue > threshold){
self.dPadState = DPadStateRight;
} else {
self.dPadState = DPadStateSelect;
}
};
}
- (void)handleDeceleratingTimer:(NSTimer *)timer {
CGFloat centerX = _thumbViewCenterXConstraintConstant + _deceleratingVelocity * 0.01;
CGFloat percent = centerX / (_trackView.frame.size.width);
CGFloat newValue = _minimumValue + ((_maximumValue - _minimumValue) * percent);
[self setValue:newValue];
if ([self isContinuous]){
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
_thumbViewCenterXConstraintConstant = _thumbViewCenterXConstraint.constant;
_deceleratingVelocity *= _decelerationRate;
if (![self isFocused] || fabs(_deceleratingVelocity) < 1){
[self stopDeceleratingTimer];
}
}
- (void)stopDeceleratingTimer {
[_deceleratingTimer invalidate];
_deceleratingTimer = nil;
_deceleratingVelocity = 0;
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
- (BOOL)isVerticalGesture:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self];
if (fabs(translation.y) > fabs(translation.x)) {
return true;
}
return false;
}
#pragma mark - Actions
- (void)panGestureWasTriggered:(UIPanGestureRecognizer *)panGestureRecognizer {
if ([self isVerticalGesture:panGestureRecognizer]){
return;
}
CGFloat translation = [panGestureRecognizer translationInView:self].x;
CGFloat velocity = [panGestureRecognizer velocityInView:self].x;
switch(panGestureRecognizer.state){
case UIGestureRecognizerStateBegan:
[self stopDeceleratingTimer];
_thumbViewCenterXConstraintConstant = _thumbViewCenterXConstraint.constant;
break;
case UIGestureRecognizerStateChanged:{
CGFloat centerX = _thumbViewCenterXConstraintConstant + translation / 5;
CGFloat percent = centerX / _trackView.frame.size.width;
CGFloat newValue = _minimumValue + ((_maximumValue - _minimumValue) * percent);
[self setValue:newValue];
if ([self isContinuous]){
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
_thumbViewCenterXConstraintConstant = _thumbViewCenterXConstraint.constant;
if (fabs(velocity) > _fineTunningVelocityThreshold){
CGFloat direction = velocity > 0 ? 1 : -1;
_deceleratingVelocity = fabs(velocity) > _decelerationMaxVelocity ? _decelerationMaxVelocity * direction : velocity;
_deceleratingTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(handleDeceleratingTimer:) userInfo:nil repeats:true];
} else {
[self stopDeceleratingTimer];
}
break;
default:
break;
}
}
- (void)leftTapWasTriggered {
CGFloat newValue = [self value]-_stepValue;
[self setValue:newValue];
}
- (void)rightTapWasTriggered {
CGFloat newValue = [self value]+_stepValue;
[self setValue:newValue];
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
for (UIPress *press in presses){
switch (press.type) {
case UIPressTypeSelect:
if(_dPadState == DPadStateLeft){
_panGestureRecognizer.enabled = false;
[self leftTapWasTriggered];
} else if (_dPadState == DPadStateRight){
_panGestureRecognizer.enabled = false;
[self rightTapWasTriggered];
} else {
_panGestureRecognizer.enabled = false;
}
break;
default:
break;
}
}
_panGestureRecognizer.enabled = true;
[super pressesBegan:presses withEvent:event];
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
[coordinator addCoordinatedAnimations:^{
[self updateStateDependantViews];
} completion:nil];
}
#pragma mark - Initializers
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
[self setupView];
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
[self setupView];
return self;
}
- (id)init {
self = [super init];
[self setupView];
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
+37
View File
@@ -0,0 +1,37 @@
//
// Based on http://stackoverflow.com/a/202511
//
#pragma mark - Enum Factory Macros
// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,
// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return @#name;
// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if ([string isEqualToString:@#name]) return name;
/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
typedef enum EnumType { \
ENUM_DEF(ENUM_VALUE) \
}EnumType; \
NSString *_Nonnull NSStringFrom##EnumType(EnumType value); \
EnumType EnumType##FromNSString(NSString *_Nonnull string); \
// Define Functions
#define DEFINE_ENUM(EnumType, ENUM_DEF) \
NSString *_Nonnull NSStringFrom##EnumType(EnumType value) \
{ \
switch(value) \
{ \
ENUM_DEF(ENUM_CASE) \
default: return @""; \
} \
} \
EnumType EnumType##FromNSString(NSString *string) \
{ \
ENUM_DEF(ENUM_STRCMP) \
return (EnumType)0; \
}
+74
View File
@@ -0,0 +1,74 @@
//
// fakes.h
// FLEX
//
// Created by Kevin Bradley on 12/22/20.
// Copyright © 2020 Flipboard. All rights reserved.
//
/*
Feel like this class requires some documentation / commentary.
there are a variaty of classes that are either forbidden or non-existant on tvOS. Initially to get things building
to find out what i needed to fix outside of missing classes i figured i'd be easiet to stub out "fake' versions
of the classes that responded to the bare minimum for API complaince to have them appear as empty elements
rather than preventing building or creating crashes.
*/
#import <UIKit/UIKit.h>
#import "FLEXMacros.h"
#import "KBSlider.h"
#import "KBDatePickerView.h"
/**
PREFACE:
Doing UISearchController integration on tvOS is a nightmare. as is ANY integration with the keyboard or having it presented to the end user.
I made an attempt to get a more native UI/UX to tvOS for this part but couldn't wrap my head around a way to get it to work in a reasonable amount of time
so i came up with this kludge to get search working in a reasonable sense as quickly as possible for tvOS.
IMPLEMENTATION:
This UIButton is added as the view of a UITabBarButtonItem as the left bar button item & there is a zero rect text field embedded in this button,
this is the chicnary necessary to get a keyboard to appear reliably when the text field becomes the first responder.
the drawback to this approach is the view appears in a mostly opaque and newly minted UISystemInputViewController with a UIKeyboard on it.
to make this approach workable I wait for 0.1 seconds and then decrease the alpha value of the 'topViewController' which happens to be
our UISystemInputViewController. this allows the user to see the changes update underneath the view while the search is executed!
*/
/// some of the iOS versions of these values are at different indexes - reused the same names and just replaced UI->TV images of these are available in tvOSAccessoryImages
typedef NS_ENUM(NSInteger, TVTableViewCellAccessoryType) {
TVTableViewCellAccessoryNone,
TVTableViewCellAccessoryDisclosureIndicator,
TVTableViewCellAccessoryCheckmark = 3,
TVTableViewCellAccessoryChevron = 5,
TVTableViewCellAccessoryChevronOpen,
TVTableViewCellAccessoryChevronDisclosureButton,
TVTableViewCellAccessoryChevronOpenDisclosureButton,
TVTableViewCellAccessoryDetailDisclosureButton = 10,
TVTableViewCellAccessoryDetailButton = 12
};
@interface KBSearchButton: UIButton <UITextFieldDelegate>
@property UISearchBar * _Nullable searchBar; //keep a reference to the search bar to add our text value to the search bar field immediately.
- (void)triggerSearchField;
@end
//UIFakeSwitch is actually just a UIButton that says TRUE/FALSE and responds to UISwitch API calls.
@interface UIFakeSwitch : UIButton <NSCoding>
@property(nullable, nonatomic, strong) UIColor *onTintColor;
@property(nullable, nonatomic, strong) UIColor *thumbTintColor;
@property(nullable, nonatomic, strong) UIImage *onImage;
@property(nullable, nonatomic, strong) UIImage *offImage;
@property(nonatomic,getter=isOn) BOOL on;
- (instancetype _Nonnull )initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER; // This class enforces a size appropriate for the control, and so the frame size is ignored.
- (nullable instancetype)initWithCoder:(NSCoder *_Nonnull)coder NS_DESIGNATED_INITIALIZER;
- (void)setOn:(BOOL)on animated:(BOOL)animated; // does not send action
+ (id _Nonnull )newSwitch;
@end
+132
View File
@@ -0,0 +1,132 @@
//
// fakes.h
// FLEX
//
// Created by Kevin Bradley on 12/22/20.
// Copyright © 2020 Flipboard. All rights reserved.
//
#import "fakes.h"
#import "NSObject+FLEX_Reflection.h"
@interface UIImage (private)
+(UIImage *)symbolImageNamed:(NSString *)symbolName;
@end
@interface KBSearchButton()
@property UITextField *searchField; //helps us get a keyboard onscreen and acts as a proxy to move text to our UISearchBar
@end
@implementation KBSearchButton
+ (instancetype)buttonWithType:(UIButtonType)buttonType {
KBSearchButton *button = [super buttonWithType:buttonType];
[button setImage:[UIImage symbolImageNamed:@"magnifyingglass"] forState:UIControlStateNormal];
button.frame = CGRectMake(0, 0, 150, 70);
UITextField *tf = [[UITextField alloc]init];
tf.clearButtonMode = UITextFieldViewModeAlways;
button.searchField = tf;
tf.delegate = button;
[button addSubview:tf];
[button addTarget:button action:@selector(triggerSearchField) forControlEvents:UIControlEventPrimaryActionTriggered];
[button addListeners];
return button;
}
- (void)textChanged:(NSNotification *)n {
self.searchBar.text = self.searchField.text;
}
- (void)addListeners {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextFieldTextDidChangeNotification object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)triggerSearchField {
self.searchField.text = self.searchBar.text;
//[self.searchBar becomeFirstResponder];
[self.searchField becomeFirstResponder];
//wait for 0.1 seconds and then decrease the opacity of UISystemInputViewController presenting our UIKeyboard
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UIViewController *vc = [self topViewController];
vc.view.alpha = 0.6;
});
}
@end
@interface UIFakeSwitch() {
BOOL _isOn;
}
@end
@implementation UIFakeSwitch
- (BOOL)isOn {
return _isOn;
}
- (void)initDefaults {
self.onTintColor = [UIColor greenColor];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self){
[self initDefaults];
}
return self;
}
- (instancetype)init {
self = [super init];
if (self){
[self initDefaults];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self){
[self initDefaults];
}
return self;
}
- (UIColor *)backgroundColor {
if ([self isOn]) return self.onTintColor;
return [super backgroundColor];
}
- (void)setOn:(BOOL)on{
[self setOn:on animated:true];
}
- (NSString *)onTitle {
return @"TRUE";
}
- (NSString *)offTitle {
return @"FALSE";
}
- (void)setOn:(BOOL)on animated:(BOOL)animated {
_isOn = on;
if (_isOn){
[self setTitle:[self onTitle] forState:UIControlStateNormal];
} else {
[self setTitle:[self offTitle] forState:UIControlStateNormal];
}
//[self sendActionsForControlEvents:[self allControlEvents]];
}
+ (id)newSwitch {
UIFakeSwitch *new = [UIFakeSwitch buttonWithType:UIButtonTypeSystem];
[new initDefaults];
return new;
}
@end
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1170"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "323F6BC5259B9DB70055BDD6"
BuildableName = "FLEX.framework"
BlueprintName = "FLEX-tvOS"
ReferencedContainer = "container:FLEX.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "323F6BC5259B9DB70055BDD6"
BuildableName = "FLEX.framework"
BlueprintName = "FLEX-tvOS"
ReferencedContainer = "container:FLEX.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+25 -1
View File
@@ -1,4 +1,5 @@
# FLEX
[![CocoaPods](https://img.shields.io/cocoapods/v/FLEX.svg)](https://cocoapods.org/?q=FLEX)
[![CocoaPods](https://img.shields.io/cocoapods/l/FLEX.svg)](https://github.com/Flipboard/FLEX/blob/master/LICENSE)
[![CocoaPods](https://img.shields.io/cocoapods/p/FLEX.svg)]()
@@ -6,7 +7,7 @@
[![Build Status](https://travis-ci.org/Flipboard/FLEX.svg?branch=master)](https://travis-ci.org/Flipboard/FLEX)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
FLEX (Flipboard Explorer) is a set of in-app debugging and exploration tools for iOS development. When presented, FLEX shows a toolbar that lives in a window above your application. From this toolbar, you can view and modify nearly every piece of state in your running application.
FLEX (Flipboard Explorer) is a set of in-app debugging and exploration tools for iOS & tvOS development. When presented, FLEX shows a toolbar that lives in a window above your application. From this toolbar, you can view and modify nearly every piece of state in your running application.
<img alt="Demo" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70185687-e842c800-16af-11ea-8ef9-9e071380a462.gif>
@@ -119,6 +120,29 @@ FLEX allows you to edit defaults that are any combination of strings, numbers, a
<img alt="NSUserDefaults Editing" width=36% height=36% src=https://user-images.githubusercontent.com/8371943/70271889-edb21800-176c-11ea-92b4-71e07d2b6ce7.gif>
## tvOS Notes
### Installation notes
Check releases or build and add the framework manually, this will need review and merging in to the main repo & a cocoapod / carthage et al update to get the framework added in any other way for tvOS versions. ./tvOSBuild.sh is added for convenience and will yield a framework in the build/Release-appletvos folder.
### General notes / usage
- When in 'select' mode press the 'menu' button to return to the toolbar
- When in 'select' mode play/pause will also work to click items and is more reliable than selection with the remote touch pad, tapping rather than clicking the touchpad is also more reliable. Tapping right on a siri remote / clicking right on an older silver remote will work as well
- Press and hold 'select' to drill down to details in views hierarchy list view controller. Tapping right on a siri remote / clicking right on an older silver remote will work as well
- When in selection mode double tap on 'play/pause' OR press and hold 'select' or 'play/pause' to bring up an alert with useful options ('view details', 'move view' and 'show views' are available there)
### FLEXInjected notes
- Easily used with a jailbreak: FLEXInjected (avail from default repos now with 'com.nito.flexinjected', bottom of featured section first item in 'Utilities')
- After toggling via FLEXInjected it will take 10 seconds after launch for the toolbar to appear, if closed - triple tap play/pause to bring it back
### Missing features
- Snapshot view (i dont think this one is going to make it in)
![tvOS](tvos_flexing.gif "tvOS FLEX")
[tvOS Video Demo](https://lbry.tv/@nitoTV:4/tvOS-FLEXING:4)
### Learning from Other Apps
The code injection is left as an exercise for the reader. :innocent:
+1
View File
@@ -0,0 +1 @@
{ Filter = { Bundles = ( "com.apple.UIKit" ); }; }
+12
View File
@@ -0,0 +1,12 @@
target = appletv:12.1.1
INSTALL_TARGET_PROCESSES = PineBoard
include $(THEOS)/makefiles/common.mk
TWEAK_NAME = FLEXInjected
FLEXInjected_FILES = Tweak.x
FLEXInjected_CFLAGS = -fobjc-arc
include $(THEOS_MAKE_PATH)/tweak.mk
+136
View File
@@ -0,0 +1,136 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include <TargetConditionals.h>
@interface NSObject (topViewController)
- (id)topViewController;
@end
@interface FLEXManager: NSObject
+(id)sharedManager;
-(void)showExplorer;
-(void)showHintsIfNecessary;
-(void)_addTVOSGestureRecognizer:(UIViewController *)explorer;
@end
@interface NSDistributedNotificationCenter : NSNotificationCenter
+ (id)defaultCenter;
- (void)addObserver:(id)arg1 selector:(SEL)arg2 name:(id)arg3 object:(id)arg4;
- (void)postNotificationName:(id)arg1 object:(id)arg2 userInfo:(id)arg3;
@end
@interface UIWindow (Additions)
- (UIViewController *)visibleViewController;
@end
@interface NSObject (Additions)
- (UIViewController *)topViewController;
@end
@implementation UIWindow (Additions)
- (UIViewController *)visibleViewController {
UIViewController *rootViewController = self.rootViewController;
return [UIWindow getVisibleViewControllerFrom:rootViewController];
}
+ (UIViewController *) getVisibleViewControllerFrom:(UIViewController *) vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [UIWindow getVisibleViewControllerFrom:[((UINavigationController *) vc) visibleViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [UIWindow getVisibleViewControllerFrom:[((UITabBarController *) vc) selectedViewController]];
} else {
if (vc.presentedViewController) {
return [UIWindow getVisibleViewControllerFrom:vc.presentedViewController];
} else {
return vc;
}
}
}
@end
@interface LSApplicationProxy: NSObject
+(id)applicationProxyForIdentifier:(id)sender;
+(id)tv_applicationFlatIcon;
-(BOOL)isContainerized;
@end
@implementation NSObject (Additions)
- (UIViewController *)topViewController {
return [[[UIApplication sharedApplication] keyWindow] visibleViewController];
}
@end
static void sendNotification(NSString *title, NSString *message, UIImage *image) {
NSMutableDictionary *dict = [NSMutableDictionary new];
dict[@"message"] = message;
dict[@"title"] = title;
dict[@"timeout"] = [NSNumber numberWithInteger:4];
if (image){
NSData *imageData = UIImagePNGRepresentation(image);
if (imageData){
dict[@"imageData"] = imageData;
}
}
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.nito.bulletinh4x/displayBulletin" object:nil userInfo:dict];
}
__attribute__ ((constructor)) static void FLEXInjected_main() {
NSBundle *bundle = [NSBundle mainBundle];
NSString *bundleID = [bundle bundleIdentifier];
NSDictionary *ourDict = [[NSDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.nito.flexinjected.plist"];
NSArray *iconBlacklist = @[@"com.apple.PineBoard", @"com.apple.HeadBoard"];
NSNumber *value = [ourDict objectForKey:bundleID];
if ([value boolValue] == YES) {
BOOL sendAlert = true;
id prox = [%c(LSApplicationProxy) applicationProxyForIdentifier:bundleID];
UIImage *icon = nil;
if (prox){
NSLog(@"[FLEXInjected] found prox: %@", prox);
if ([prox isContainerized]){
icon = nil;
} else {
if ([prox respondsToSelector:@selector(tv_applicationFlatIcon)] && ![iconBlacklist containsObject:bundleID]){
icon = [prox tv_applicationFlatIcon];
}
}
} else { //no prox found
NSString *mcsPath = @"/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices";
[[NSBundle bundleWithPath:mcsPath] load];
prox = [%c(LSApplicationProxy) applicationProxyForIdentifier:bundleID];
if ([prox isContainerized]){
icon = nil;
} else {
if ([prox respondsToSelector:@selector(tv_applicationFlatIcon)] && ![iconBlacklist containsObject:bundleID]){
icon = [prox tv_applicationFlatIcon];
}
}
}
NSLog(@"[FLEXInjected] found icon: %@", icon);
if (sendAlert){
NSString *message = [NSString stringWithFormat:@"Injected into bundle: %@", bundleID];
sendNotification(@"FLEXInjected", message, icon);
NSLog(@"[FLEXInjected) bundle ID %@", bundleID);
}
NSLog(@"[FLEXInjected] post send alert");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"[FLEXInjected] weouchea...");
NSString *p = @"/Library/Frameworks/FLEX.framework";
NSBundle *bundle = [NSBundle bundleWithPath:p];
[bundle load];
id flexManager = [%c(FLEXManager) sharedManager];
UIViewController *tvc = [[UIApplication sharedApplication] topViewController];
if([tvc respondsToSelector: @selector(tabBarController)]){
UITabBarController *tabBar = [tvc tabBarController];
if (tabBar) tvc = tabBar;
}
NSLog(@"[FLEXInjected] top view controller: %@ violated...", tvc);
[flexManager _addTVOSGestureRecognizer:tvc];
[flexManager showExplorer];
if ([flexManager respondsToSelector:@selector(showHintsIfNecessary)]){
[flexManager showHintsIfNecessary];
}
});
}
}
+10
View File
@@ -0,0 +1,10 @@
Package: com.nito.flexinjected
Name: FLEXInjected
Depends: mobilesubstrate, preferenceloader (>=1.5-15), applist (>=1.3-11), libflex (>=4.3.0-6)
Version: 1.5
Architecture: appletvos-arm64
Description: Inject FLEX into your favorite Applications utilizing applist
Maintainer: Kevin Bradley
Author: Kevin Bradley
Section: Tweaks
Depiction: https://nitosoft.com/ATV4/flex/flex.json

Some files were not shown because too many files have changed in this diff Show More