Compare commits
15 Commits
flexing
...
4.3.0-7-tvOS
| Author | SHA1 | Date | |
|---|---|---|---|
| 691ca1132e | |||
| 5e005db93b | |||
| 23fc5ea533 | |||
| 09300aa62e | |||
| 7c5b2308f0 | |||
| b0a9cd2707 | |||
| b921bde7fb | |||
| 8b65f2a8a7 | |||
| 59d22e2c01 | |||
| 6f7969ab18 | |||
| 1d9d6e80e3 | |||
| 56e5d7ddf9 | |||
| 8542a74fb9 | |||
| 1a8759615a | |||
| 8300a30ca3 |
@@ -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,7 +16,9 @@
|
||||
#import "FLEXResources.h"
|
||||
#import "UIBarButtonItem+FLEX.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#import "FLEXTV.h"
|
||||
#endif
|
||||
@interface Block : NSObject
|
||||
- (void)invoke;
|
||||
@end
|
||||
@@ -39,6 +41,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
@property (nonatomic) UIBarButtonItem *middleToolbarItem;
|
||||
@property (nonatomic) UIBarButtonItem *middleLeftToolbarItem;
|
||||
@property (nonatomic) UIBarButtonItem *leftmostToolbarItem;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXTableViewController
|
||||
@@ -52,7 +55,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 +96,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 +115,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 +129,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 +226,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 +260,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 +282,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 +306,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 +341,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
if (!self.isViewLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
self.toolbarItems = @[
|
||||
self.leftmostToolbarItem,
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
@@ -331,7 +359,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 +487,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 +506,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 +535,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
_tableHeaderViewContainer = nil;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (UIView *)tableHeaderViewContainer {
|
||||
@@ -548,7 +587,7 @@ CGFloat const kFLEXDebounceForExpensiveIO = 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
#pragma mark UISearchControllerDelegate
|
||||
|
||||
- (void)willPresentSearchController:(UISearchController *)searchController {
|
||||
@@ -572,19 +611,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
|
||||
|
||||
@@ -101,7 +101,9 @@
|
||||
image:copyIcon
|
||||
identifier:nil
|
||||
handler:^(__kindof UIAction *action) {
|
||||
#if !TARGET_OS_TV
|
||||
UIPasteboard.generalPasteboard.string = value;
|
||||
#endif
|
||||
}
|
||||
];
|
||||
if (!value.length) {
|
||||
|
||||
@@ -201,4 +201,10 @@ NSString * const kCarouselCellReuseIdentifier = @"kCarouselCellReuseIdentifier";
|
||||
[self sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (BOOL)isEnabled {
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,18 @@
|
||||
#import "FLEXArgumentInputColorView.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#import "FLEXTV.h"
|
||||
#endif
|
||||
@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 +39,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 +68,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,17 @@
|
||||
|
||||
#import "FLEXArgumentInputDateView.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
#if TARGET_OS_TV
|
||||
#import "FLEXTV.h"
|
||||
#endif
|
||||
@interface FLEXArgumentInputDateView ()
|
||||
|
||||
#if TARGET_OS_TV
|
||||
@property (nonatomic) KBDatePickerView *datePicker;
|
||||
#else
|
||||
@property (nonatomic) UIDatePicker *datePicker;
|
||||
|
||||
#endif
|
||||
@end
|
||||
|
||||
@implementation FLEXArgumentInputDateView
|
||||
@@ -20,11 +26,18 @@
|
||||
- (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];
|
||||
self.datePicker.showDateLabel = true;
|
||||
#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 "FLEXTV.h"
|
||||
@interface FLEXArgumentInputSwitchView ()
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@property (nonatomic) UISwitch *inputSwitch;
|
||||
|
||||
#else
|
||||
@property (nonatomic) UIFLEXSwitch *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 = [UIFLEXSwitch newSwitch];
|
||||
[self.inputSwitch addTarget:self action:@selector(changeSwitchValue:) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
#endif
|
||||
[self addSubview:self.inputSwitch];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)changeSwitchValue:(UIFLEXSwitch *)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;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#import "FLEXUtility.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXTableView.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
|
||||
@interface FLEXBookmarksViewController ()
|
||||
@property (nonatomic, copy) NSArray *bookmarks;
|
||||
@@ -32,8 +33,9 @@
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
self.navigationController.hidesBarsOnSwipe = NO;
|
||||
#endif
|
||||
self.tableView.allowsMultipleSelectionDuringEditing = YES;
|
||||
|
||||
[self reloadData];
|
||||
@@ -42,6 +44,13 @@
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[self setupDefaultBarItems];
|
||||
#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
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +65,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 +73,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 +210,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 +224,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,140 @@ 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 View Controllers").handler(^(NSArray<NSString *> *strings) {
|
||||
UIViewController *list = [FLEXViewControllersViewController
|
||||
controllersForViews:self.viewsAtTapPoint
|
||||
];
|
||||
[self presentViewController:
|
||||
[FLEXNavigationController withRootViewController:list
|
||||
] animated:YES completion:nil];
|
||||
});
|
||||
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 +579,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
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -472,15 +665,22 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
[toolbar.selectedViewDescriptionContainer addGestureRecognizer:leftSwipe];
|
||||
// [toolbar.selectedViewDescriptionContainer addGestureRecognizer:rightSwipe];
|
||||
|
||||
// Long press gesture to present tabs manager
|
||||
[toolbar.globalsItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleToolbarShowTabsGesture:)
|
||||
]];
|
||||
UILongPressGestureRecognizer *globalLongPressGesture = [[UILongPressGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleToolbarShowTabsGesture:)];
|
||||
#if TARGET_OS_TV
|
||||
globalLongPressGesture.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeSelect]];
|
||||
#endif
|
||||
|
||||
// Long press gesture to present tabs manager
|
||||
[toolbar.globalsItem addGestureRecognizer:globalLongPressGesture];
|
||||
|
||||
UILongPressGestureRecognizer *selectLongPressGesture = [[UILongPressGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleToolbarWindowManagerGesture:)];
|
||||
#if TARGET_OS_TV
|
||||
selectLongPressGesture.allowedPressTypes = @[[NSNumber numberWithInteger:UIPressTypePlayPause],[NSNumber numberWithInteger:UIPressTypeSelect]];
|
||||
#endif
|
||||
// Long press gesture to present window manager
|
||||
[toolbar.selectItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(handleToolbarWindowManagerGesture:)
|
||||
]];
|
||||
[toolbar.selectItem addGestureRecognizer:selectLongPressGesture];
|
||||
|
||||
// Long press gesture to present view controllers at tap
|
||||
[toolbar.hierarchyItem addGestureRecognizer:[[UILongPressGestureRecognizer alloc]
|
||||
@@ -560,8 +760,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 +773,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 +785,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 +858,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 +1071,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 +1093,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 +1106,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 +1142,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 +1185,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#import "FLEXExplorerViewController.h"
|
||||
#import "FLEXGlobalsViewController.h"
|
||||
#import "FLEXBookmarksViewController.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
|
||||
@interface FLEXTabsViewController ()
|
||||
@property (nonatomic, copy) NSArray<UINavigationController *> *openTabs;
|
||||
@@ -39,7 +40,9 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
self.title = @"Open Tabs";
|
||||
#if !TARGET_OS_TV
|
||||
self.navigationController.hidesBarsOnSwipe = NO;
|
||||
#endif
|
||||
self.tableView.allowsMultipleSelectionDuringEditing = YES;
|
||||
|
||||
[self reloadData:NO];
|
||||
@@ -48,6 +51,15 @@
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[self setupDefaultBarItems];
|
||||
#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];
|
||||
}
|
||||
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addTabButtonPressed:)];
|
||||
self.navigationItem.leftBarButtonItem = addButton;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
@@ -106,6 +118,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 +129,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 +145,7 @@
|
||||
];
|
||||
|
||||
self.toolbarItems.firstObject.tintColor = FLEXColor.destructiveColor;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (FLEXExplorerViewController *)corePresenter {
|
||||
@@ -287,8 +303,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 +325,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:[UIScreen mainScreen].bounds];
|
||||
self.webView.delegate = self;
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -38,7 +53,11 @@
|
||||
self = [self initWithNibName:nil bundle:nil];
|
||||
if (self) {
|
||||
self.originalText = text;
|
||||
#if TARGET_OS_TV
|
||||
NSString *htmlString = [NSString stringWithFormat:@"<head><meta name='viewport' content='initial-scale=1.0'></head><body>%@</body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
|
||||
#else
|
||||
NSString *htmlString = [NSString stringWithFormat:@"<head><meta name='viewport' content='initial-scale=1.0'></head><body><pre>%@</pre></body>", [FLEXUtility stringByEscapingHTMLEntitiesInString:text]];
|
||||
#endif
|
||||
[self.webView loadHTMLString:htmlString baseURL:nil];
|
||||
}
|
||||
return self;
|
||||
@@ -55,28 +74,66 @@
|
||||
|
||||
- (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 {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self.view addSubview:self.webView];
|
||||
#if !TARGET_OS_TV
|
||||
self.webView.frame = self.view.bounds;
|
||||
self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
#endif
|
||||
if (self.originalText.length > 0) {
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonTapped:)];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 +152,7 @@
|
||||
decisionHandler(policy);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark - Class Helpers
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXObjectExplorerViewController.h"
|
||||
#import <mach-o/loader.h>
|
||||
#import "FLEXTV.h"
|
||||
|
||||
@interface FLEXFileBrowserTableViewCell : UITableViewCell
|
||||
@end
|
||||
@@ -32,7 +33,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 +100,9 @@ typedef NS_ENUM(NSUInteger, FLEXFileBrowserSortAttribute) {
|
||||
target:self
|
||||
action:@selector(sortDidTouchUpInside:)]
|
||||
]];
|
||||
#if TARGET_OS_TV
|
||||
[self addlongPressGestureRecognizer];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)sortDidTouchUpInside:(UIBarButtonItem *)sortButton {
|
||||
@@ -231,6 +237,77 @@ 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!");
|
||||
NSURL *fileURL = [NSURL fileURLWithPath:fullPath];
|
||||
BOOL canOpenURL = [[UIApplication sharedApplication] canOpenURL:fileURL];
|
||||
FXLog(@"can open file: %d", canOpenURL);
|
||||
if (canOpenURL){
|
||||
NSString *laws = [@[@"LS",@"Application",@"Workspace"] componentsJoinedByString:@""];
|
||||
NSString *df = [@[@"default",@"Workspace"] componentsJoinedByString:@""];
|
||||
id ws = [NSClassFromString(laws) valueForKey:df];
|
||||
[ws openURL:fileURL];
|
||||
}
|
||||
});
|
||||
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 +412,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 +421,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 +440,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 +474,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 +537,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 +569,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 +640,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,11 +33,19 @@
|
||||
}
|
||||
|
||||
+ (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 {
|
||||
#if TARGET_OS_TV
|
||||
self = [FLEXKBToolbarButton buttonWithType:UIButtonTypeSystem];
|
||||
#else
|
||||
self = [super init];
|
||||
#endif
|
||||
if (self) {
|
||||
_title = title;
|
||||
self.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXRuntimeBrowserToolbar.h"
|
||||
#import "FLEXMethod.h"
|
||||
|
||||
#import <TargetConditionals.h>
|
||||
@protocol FLEXKeyPathSearchControllerDelegate <UITableViewDataSource>
|
||||
|
||||
@property (nonatomic, readonly) UITableView *tableView;
|
||||
@@ -34,5 +34,7 @@
|
||||
|
||||
- (void)didSelectKeyPathOption:(NSString *)text;
|
||||
- (void)didPressButton:(NSString *)text insertInto:(UISearchBar *)searchBar;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)basicSearchWithText:(NSString *)text;
|
||||
#endif
|
||||
@end
|
||||
|
||||
@@ -94,6 +94,14 @@
|
||||
return classes;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)basicSearchWithText:(NSString *)text {
|
||||
self.keyPath = [FLEXRuntimeKeyPathTokenizer tokenizeString:text];
|
||||
[self searchBar:self.delegate.searchController.searchBar textDidChange:text];
|
||||
//[self updateTable];
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark Key path stuff
|
||||
|
||||
- (void)didSelectKeyPathOption:(NSString *)text {
|
||||
@@ -231,6 +239,7 @@
|
||||
}
|
||||
|
||||
- (BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
|
||||
LOG_SELF;
|
||||
// Check if character is even legal
|
||||
if (![FLEXRuntimeKeyPathTokenizer allowedInKeyPath:text]) {
|
||||
return NO;
|
||||
@@ -257,6 +266,7 @@
|
||||
}
|
||||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
|
||||
LOG_SELF;
|
||||
[_timer invalidate];
|
||||
|
||||
// Schedule update timer
|
||||
@@ -310,7 +320,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;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#import "FLEXObjectExplorerFactory.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXRuntimeClient.h"
|
||||
#import "FLEXTV.h"
|
||||
|
||||
@interface FLEXObjcRuntimeViewController () <FLEXKeyPathSearchControllerDelegate>
|
||||
|
||||
@@ -59,12 +60,19 @@
|
||||
FLEXKeyPathSearchController *keyPathController = [FLEXKeyPathSearchController delegate:self];
|
||||
_keyPathController = keyPathController;
|
||||
_keyPathController.toolbar = [FLEXRuntimeBrowserToolbar toolbarWithHandler:^(NSString *text, BOOL suggestion) {
|
||||
LOG_SELF;
|
||||
if (suggestion) {
|
||||
[keyPathController didSelectKeyPathOption:text];
|
||||
} else {
|
||||
[keyPathController didPressButton:text insertInto:searchBar];
|
||||
}
|
||||
} suggestions:keyPathController.suggestions];
|
||||
#if TARGET_OS_TV
|
||||
KBSearchButton * searchButton = (KBSearchButton*)[self.navigationItem leftBarButtonItem].customView;
|
||||
if ([searchButton respondsToSelector:@selector(keyPathController)]){
|
||||
[searchButton setKeyPathController:keyPathController];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
@@ -91,7 +99,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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 "FLEXTV.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) UIFLEXSwitch *observerSwitch;
|
||||
@property (nonatomic, readonly) UIFLEXSwitch *cacheMediaSwitch;
|
||||
@property (nonatomic, readonly) UIFLEXSwitch *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 = [UIFLEXSwitch newSwitch];
|
||||
_cacheMediaSwitch = [UIFLEXSwitch newSwitch];
|
||||
_jsonViewerSwitch = [UIFLEXSwitch newSwitch];
|
||||
_cacheLimitSlider = [[KBSlider alloc] initWithFrame:CGRectMake(0, 0, 400, 53)];
|
||||
#endif
|
||||
|
||||
self.observerSwitch.on = FLEXNetworkObserver.enabled;
|
||||
[self.observerSwitch addTarget:self
|
||||
@@ -63,8 +80,11 @@
|
||||
action:@selector(cacheLimitAdjusted:)
|
||||
forControlEvents:UIControlEventValueChanged
|
||||
];
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
UISlider *slider = self.cacheLimitSlider;
|
||||
#else
|
||||
KBSlider *slider = self.cacheLimitSlider;
|
||||
#endif
|
||||
self.cacheLimitValue = FLEXNetworkRecorder.defaultRecorder.responseCacheByteLimit;
|
||||
const NSUInteger fiftyMega = 50 * 1024 * 1024;
|
||||
slider.minimumValue = 0;
|
||||
@@ -87,19 +107,19 @@
|
||||
|
||||
#pragma mark - Settings Actions
|
||||
|
||||
- (void)networkDebuggingToggled:(UISwitch *)sender {
|
||||
- (void)networkDebuggingToggled:(UIFLEXSwitch *)sender {
|
||||
FLEXNetworkObserver.enabled = sender.isOn;
|
||||
}
|
||||
|
||||
- (void)cacheMediaResponsesToggled:(UISwitch *)sender {
|
||||
- (void)cacheMediaResponsesToggled:(UIFLEXSwitch*)sender {
|
||||
FLEXNetworkRecorder.defaultRecorder.shouldCacheMediaResponses = sender.isOn;
|
||||
}
|
||||
|
||||
- (void)jsonViewerSettingToggled:(UISwitch *)sender {
|
||||
- (void)jsonViewerSettingToggled:(UIFLEXSwitch *)sender {
|
||||
[NSUserDefaults.standardUserDefaults flex_toggleBoolForKey:kFLEXDefaultsRegisterJSONExplorerKey];
|
||||
}
|
||||
|
||||
- (void)cacheLimitAdjusted:(UISlider *)sender {
|
||||
- (void)cacheLimitAdjusted:(KBSlider *)sender {
|
||||
self.cacheLimitValue = sender.value;
|
||||
}
|
||||
|
||||
@@ -172,7 +192,11 @@
|
||||
[cell.contentView addSubview:self.cacheLimitSlider];
|
||||
|
||||
CGRect container = cell.contentView.frame;
|
||||
#if !TARGET_OS_TV
|
||||
UISlider *slider = self.cacheLimitSlider;
|
||||
#else
|
||||
KBSlider *slider = self.cacheLimitSlider;
|
||||
#endif
|
||||
[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,16 @@
|
||||
#import "FLEXShortcutsSection.h"
|
||||
#import "NSUserDefaults+FLEX.h"
|
||||
#import <objc/runtime.h>
|
||||
#import <TargetConditionals.h>
|
||||
#import "FLEXBookmarksViewController.h"
|
||||
#if TARGET_OS_TV
|
||||
#import "FLEXTV.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;
|
||||
@@ -116,6 +123,12 @@
|
||||
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
|
||||
leftSwipe.delegate = self;
|
||||
rightSwipe.delegate = self;
|
||||
#if TARGET_OS_TV
|
||||
[leftSwipe removeTarget:self action:@selector(handleSwipeGesture:)];
|
||||
[rightSwipe removeTarget:self action:@selector(handleSwipeGesture:)];
|
||||
[leftSwipe addTarget:self action:@selector(handleSwipeGestureTV:)];
|
||||
[rightSwipe addTarget:self action:@selector(handleSwipeGestureTV:)];
|
||||
#endif
|
||||
[self.tableView addGestureRecognizer:leftSwipe];
|
||||
[self.tableView addGestureRecognizer:rightSwipe];
|
||||
|
||||
@@ -131,10 +144,42 @@
|
||||
object:nil
|
||||
];
|
||||
}
|
||||
#if TARGET_OS_TV
|
||||
[self addlongPressGestureRecognizer];
|
||||
[self addTVOSActionButton];
|
||||
#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 TARGET_OS_TV
|
||||
if ( gesture.state == UIGestureRecognizerStateEnded) {
|
||||
UITableView *tv = [self tableView];
|
||||
//naughty naughty
|
||||
NSString *focusedIndexPath = [@[@"_focused", @"Cell", @"Index", @"Path"] componentsJoinedByString:@""];
|
||||
NSString *focusedCell = [@[@"_focused", @"Cell"] componentsJoinedByString:@""];
|
||||
NSIndexPath *focus = [tv valueForKey:focusedIndexPath];
|
||||
UITableViewCell *cell = [tv valueForKey:focusedCell];
|
||||
FXLog(@"focusedIndexPath: %@ accessoryType: %lu", focus, cell.accessoryType);
|
||||
if (cell.accessoryType != TVTableViewCellAccessoryDisclosureIndicator){
|
||||
[self tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:focus];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
|
||||
#if !TARGET_OS_TV
|
||||
[self.navigationController setToolbarHidden:NO animated:YES];
|
||||
#endif
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -217,17 +262,36 @@
|
||||
[super reloadData];
|
||||
}
|
||||
|
||||
- (void)addTVOSActionButton {
|
||||
UIBarButtonItem *actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareButtonPressed:)];
|
||||
self.navigationItem.rightBarButtonItem = actionButton;
|
||||
}
|
||||
|
||||
- (void)shareButtonPressed:(UIBarButtonItem *)sender {
|
||||
[FLEXAlert makeSheet:^(FLEXAlert *make) {
|
||||
make.button(@"Add to Bookmarks").handler(^(NSArray<NSString *> *strings) {
|
||||
[FLEXBookmarkManager.bookmarks addObject:self.object];
|
||||
});
|
||||
#if !TARGET_OS_TV
|
||||
make.button(@"Copy Description").handler(^(NSArray<NSString *> *strings) {
|
||||
UIPasteboard.generalPasteboard.string = self.explorer.objectDescription;
|
||||
});
|
||||
make.button(@"Copy Address").handler(^(NSArray<NSString *> *strings) {
|
||||
UIPasteboard.generalPasteboard.string = [FLEXUtility addressOfObject:self.object];
|
||||
});
|
||||
#else
|
||||
make.button(@"Show Bookmarks").handler(^(NSArray<NSString *> *strings) {
|
||||
FLEXBookmarksViewController *bookmarks = [FLEXBookmarksViewController new];
|
||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:bookmarks];
|
||||
[self presentViewController:navController animated:true completion:nil];
|
||||
});
|
||||
make.button(@"Show Tabs").handler(^(NSArray<NSString *> *strings) {
|
||||
FLEXTabsViewController *tabs = [FLEXTabsViewController new];
|
||||
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tabs];
|
||||
[self presentViewController:navController animated:true completion:nil];
|
||||
});
|
||||
|
||||
#endif
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self source:sender];
|
||||
}
|
||||
@@ -242,17 +306,41 @@
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
- (void)handleSwipeGestureTV:(UISwipeGestureRecognizer *)gesture {
|
||||
if (gesture.state == UIGestureRecognizerStateEnded) {
|
||||
switch (gesture.direction) {
|
||||
case UISwipeGestureRecognizerDirectionLeft:
|
||||
if (self.selectedScope > 0) {
|
||||
self.selectedScope -= 1;
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
break;
|
||||
case UISwipeGestureRecognizerDirectionRight:
|
||||
if (self.selectedScope != self.explorer.classHierarchy.count - 1) {
|
||||
self.selectedScope += 1;
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleSwipeGesture:(UISwipeGestureRecognizer *)gesture {
|
||||
if (gesture.state == UIGestureRecognizerStateEnded) {
|
||||
switch (gesture.direction) {
|
||||
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 +352,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 +360,13 @@
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if ([g2 isKindOfClass:[UIPanGestureRecognizer class]]) {
|
||||
if (g2 == self.tableView.panGestureRecognizer) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -374,7 +470,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 "FLEXTV.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 "FLEXTV.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 "FLEXTV.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,11 @@
|
||||
}
|
||||
|
||||
+ (CGFloat)topMargin {
|
||||
#if !TARGET_OS_TV
|
||||
return 2.0;
|
||||
#else
|
||||
return 20.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +126,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 +138,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 +154,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 +171,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 "FLEXTV.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;
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets;
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets aboveView:(UIView *)sibling;
|
||||
- (void)flex_pinEdgesToSuperviewWithInsets:(UIEdgeInsets)insets belowView:(UIView *)sibling;
|
||||
- (UIView *)flex_findFirstSubviewWithClass:(Class)theClass;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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 {
|
||||
@@ -63,4 +79,18 @@
|
||||
]];
|
||||
}
|
||||
|
||||
- (UIView *)flex_findFirstSubviewWithClass:(Class)theClass {
|
||||
if ([self isMemberOfClass:theClass]) {
|
||||
return self;
|
||||
}
|
||||
|
||||
for (UIView *v in self.subviews) {
|
||||
UIView *theView = [v flex_findFirstSubviewWithClass:theClass];
|
||||
if (theView != nil) {
|
||||
return theView;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 "FLEXTV.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,28 @@ 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
|
||||
NSString *focusedIndexPath = [@[@"_focused", @"Cell", @"Index", @"Path"] componentsJoinedByString:@""];
|
||||
NSIndexPath *focus = [tv valueForKey:focusedIndexPath];
|
||||
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 +93,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 +257,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
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// FLEXTV.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"
|
||||
#import "FLEXKeyPathSearchController.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.
|
||||
@property (weak) FLEXKeyPathSearchController *keyPathController; //optional
|
||||
- (void)triggerSearchField;
|
||||
@end
|
||||
|
||||
//UIFLEXSwitch is actually just a UIButton that says TRUE/FALSE and responds to UISwitch API calls.
|
||||
|
||||
@interface UIFLEXSwitch : UIButton <NSCoding>
|
||||
@property(nullable, nonatomic, strong) UIColor *onTintColor;
|
||||
@property(nullable, nonatomic, strong) UIColor *thumbTintColor; //here for protocol adherence - ignored
|
||||
@property(nullable, nonatomic, strong) UIImage *onImage; //ditto above
|
||||
@property(nullable, nonatomic, strong) UIImage *offImage; //ditto
|
||||
@property(nonatomic,getter=isOn) BOOL on;
|
||||
- (instancetype _Nonnull )initWithFrame:(CGRect)frame; // This class enforces a size appropriate for the control, and so the frame size is ignored.
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *_Nonnull)coder;
|
||||
- (void)setOn:(BOOL)on animated:(BOOL)animated; // does not send action
|
||||
+ (id _Nonnull )newSwitch;
|
||||
@end
|
||||
|
||||
@interface LSApplicationWorkspace: NSObject
|
||||
+(id _Nonnull )defaultWorkspace;
|
||||
-(BOOL)openURL:(NSURL *_Nonnull)url;
|
||||
@end
|
||||
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// FLEXTV.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Kevin Bradley on 12/22/20.
|
||||
// Copyright © 2020 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTV.h"
|
||||
#import "NSObject+FLEX_Reflection.h"
|
||||
#import "UIView+FLEX_Layout.h"
|
||||
#import "FLEXRuntimeKeyPathTokenizer.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;
|
||||
if (self.keyPathController){
|
||||
[self.keyPathController basicSearchWithText:self.searchBar.text];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addListeners {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextFieldTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)clearSearchField:(id)sender {
|
||||
self.searchField.text = nil;
|
||||
[self textChanged:nil];
|
||||
}
|
||||
|
||||
- (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;
|
||||
/*
|
||||
//FLEXRuntimeBrowserToolbar *toolbar = [self.keyPathController toolbar];
|
||||
FLEXRuntimeBrowserToolbar *toolbar = [FLEXRuntimeBrowserToolbar toolbarWithHandler:^(NSString *text, BOOL suggestion) {
|
||||
FXLog(@"enteredText: %@ suggestion: %d", text, suggestion);
|
||||
if (suggestion) {
|
||||
[self.keyPathController didSelectKeyPathOption:text];
|
||||
} else {
|
||||
self.searchField.text = text;
|
||||
[self textChanged:nil];
|
||||
[self.keyPathController didPressButton:text insertInto:self.searchBar];
|
||||
}
|
||||
} suggestions:self.keyPathController.suggestions];
|
||||
toolbar.frame = CGRectMake(400,630,1080,44);
|
||||
self.keyPathController.toolbar = toolbar;
|
||||
*/
|
||||
UIButton *clearButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
clearButton.frame = CGRectMake(860,900,200,86);
|
||||
[clearButton setTitle:@"clear" forState:UIControlStateNormal];
|
||||
[clearButton addTarget:self action:@selector(clearSearchField:) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
if ([[vc view] respondsToSelector:@selector(contentView)]){
|
||||
UIView *contentView = [vc.view valueForKey:@"contentView"];
|
||||
[contentView addSubview:clearButton];
|
||||
//[contentView addSubview:toolbar];
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface UIFLEXSwitch() {
|
||||
BOOL _isOn;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation UIFLEXSwitch
|
||||
|
||||
- (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)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self){
|
||||
[self initDefaults];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setOn:(BOOL)on{
|
||||
[self setOn:on animated:true];
|
||||
}
|
||||
|
||||
- (NSString *)onTitle {
|
||||
return @"ON";
|
||||
}
|
||||
|
||||
- (NSString *)offTitle {
|
||||
return @"OFF";
|
||||
}
|
||||
|
||||
/*
|
||||
`<UIFLEXSwitch: 0x1009b3010; baseClass = UIButton; frame = (50 50; 200 70); opaque = NO; gestureRecognizers = <NSArray: 0x2814ba4f0>; layer = <CALayer: 0x281a811c0>>
|
||||
| <_UIFloatingContentView: 0x10ec4a820; frame = (0 0; 200 70); opaque = NO; layer = <CALayer: 0x281a92ce0>>
|
||||
| | <UIView: 0x1008744e0; frame = (0 -0.25; 200 70); transform3D = [0.8, 0, 0, 0; 0, 0.8, 0, 0; 0, 0, 1, -0.001; 0, 0, 0, 1]; alpha = 0; opaque = NO; layer = <CALayer: 0x281a93140>>
|
||||
| | <_UIFloatingContentTransformView: 0x10ec3ede0; frame = (0 0; 200 70); layer = <CATransformLayer: 0x281a92c20>>
|
||||
| | | <_UIFloatingContentCornerRadiusAnimatingView: 0x10ec35500; frame = (0 0; 200 70); opaque = NO; layer = <CALayer: 0x281a93160>>
|
||||
| | | | <UIVisualEffectView: 0x10097e050; frame = (0 0; 200 70); layer = <CALayer: 0x281a82b00>> effect=<UIBlurEffect: 0x2819a91b0> style=UIBlurEffectStyleATVSemiAutomatic invertedAutomaticStyle
|
||||
| | | | | <_UIVisualEffectBackdropView: 0x10fa0b4b0; frame = (0 0; 200 70); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x281a82d40>>
|
||||
| | | | | <_UIVisualEffectSubview: 0x100961a50; frame = (0 0; 200 70); alpha = 0.4; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x281a82ee0>>
|
||||
| | | <_UIFloatingContentCornerRadiusAnimatingScreenScaleInheritingView: 0x10ec4f910; frame = (0 0; 200 70); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x281a92be0>>
|
||||
| | | | <_UIFloatingContentCornerRadiusAnimatingView: 0x1008af590; frame = (0 0; 200 70); layer = <CALayer: 0x281a92b60>>
|
||||
| | | | <UIView: 0x10ec547b0; frame = (0 0; 200 70); layer = <CALayer: 0x281a9e900>>
|
||||
| | | | | <UIButtonLabel: 0x100868630; frame = (65 12; 70 46); text = 'OFF'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x2838d3520>>`
|
||||
|
||||
|
||||
UIButtons have a crazy heirarchy on tvOS - and changing the background color of the ACTUAL button isn't supported (as far as im aware) so this is one way to do it!
|
||||
|
||||
*/
|
||||
|
||||
- (void)_updateBackgroundForMode {
|
||||
if ([self isOn]){
|
||||
[self setBackgroundColor:self.onTintColor];
|
||||
} else {
|
||||
[self setBackgroundColor:[UIColor colorWithWhite:0.4 alpha:0.5]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(UIColor *)backgroundColor {
|
||||
//_UIVisualEffectSubview
|
||||
NSString *cleverTrick = [@[@"_",@"UI",@"Visual",@"Effect",@"Subview"] componentsJoinedByString:@""];
|
||||
UIView *bgView = [self flex_findFirstSubviewWithClass:objc_getClass([cleverTrick UTF8String])]; //this class has been around since tvOS 9, so this is definitely safe.
|
||||
if (bgView) {
|
||||
bgView.backgroundColor = backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self _updateBackgroundForMode];
|
||||
}
|
||||
|
||||
- (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 _updateBackgroundForMode];
|
||||
//[self sendActionsForControlEvents:[self allControlEvents]];
|
||||
}
|
||||
|
||||
+ (id)newSwitch {
|
||||
UIFLEXSwitch *new = [UIFLEXSwitch buttonWithType:UIButtonTypeSystem];
|
||||
[new initDefaults];
|
||||
return new;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "Macros.h"
|
||||
#define NUMBER_OF_CELLS 100000
|
||||
|
||||
// Enums are all defined like this to make it easier to convert them to / from string versions of themselves.
|
||||
#define TABLE_TAG(XX) \
|
||||
XX(KBTableViewTagMonths, = 501) \
|
||||
XX(KBTableViewTagDays, )\
|
||||
XX(KBTableViewTagYears, )\
|
||||
XX(KBTableViewTagHours, )\
|
||||
XX(KBTableViewTagMinutes, )\
|
||||
XX(KBTableViewTagAMPM, )\
|
||||
XX(KBTableViewTagWeekday, )\
|
||||
XX(KBTableViewTagCDHours,)\
|
||||
XX(KBTableViewTagCDMinutes,)\
|
||||
XX(KBTableViewTagCDSeconds,)
|
||||
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;
|
||||
@end
|
||||
|
||||
@interface KBDatePickerView: UIControl <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@property (nullable, nonatomic, strong) NSLocale *locale; // default is [NSLocale currentLocale]. setting nil returns to default
|
||||
@property (null_resettable, nonatomic, copy) NSCalendar *calendar; // default is [NSCalendar currentCalendar]. setting nil returns to default
|
||||
@property (nullable, nonatomic, strong) NSTimeZone *timeZone; // default is nil. use current time zone or time zone from calendar
|
||||
|
||||
@property (nonnull, nonatomic, strong) NSDate *date;
|
||||
@property (nullable, nonatomic, strong) NSDate *minimumDate;
|
||||
@property (nullable, nonatomic, strong) NSDate *maximumDate;
|
||||
|
||||
@property (nonatomic) NSTimeInterval countDownDuration; // for KBDatePickerModeCountDownTimer, ignored otherwise. default is 0.0. limit is 23:59 (86,399 seconds). value being set is div 60 (drops remaining seconds).
|
||||
@property (nonatomic) NSInteger minuteInterval; // display minutes wheel with interval. interval must be evenly divided into 60. default is 1. min is 1, max is 30 (***not used yet***)
|
||||
|
||||
@property BOOL showDateLabel; //defaults to false - whether or not to show a label below the picker for a pretty printed version of the date
|
||||
@property KBDatePickerMode datePickerMode;
|
||||
@property NSInteger topOffset;
|
||||
+(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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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; \
|
||||
}
|
||||
@@ -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>
|
||||
+949
-13
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>
|
||||
@@ -1,4 +1,5 @@
|
||||
# FLEX
|
||||
|
||||
[](https://cocoapods.org/?q=FLEX)
|
||||
[](https://github.com/Flipboard/FLEX/blob/master/LICENSE)
|
||||
[]()
|
||||
@@ -6,7 +7,7 @@
|
||||
[](https://travis-ci.org/Flipboard/FLEX)
|
||||
[](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 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:
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{ Filter = { Bundles = ( "com.apple.UIKit" ); }; }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user