Swiftgram/submodules/LegacyComponents/Sources/TGMenuSheetController.m
2021-03-27 10:23:35 +05:00

1203 lines
40 KiB
Objective-C

#import "TGMenuSheetController.h"
#import "LegacyComponentsInternal.h"
#import "LegacyComponentsGlobals.h"
#import "TGNavigationController.h"
#import "TGOverlayController.h"
#import "TGOverlayControllerWindow.h"
#import "TGImageUtils.h"
#import "TGHacks.h"
#import <SSignalKit/SSignalKit.h>
#import "TGMenuSheetView.h"
#import "TGMenuSheetDimView.h"
#import "TGMenuSheetItemView.h"
#import "TGMenuSheetCollectionView.h"
#import <LegacyComponents/TGObserverProxy.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
const CGFloat TGMenuSheetPadMenuWidth = 375.0f;
const CGFloat TGMenuSheetDefaultStatusBarHeight = 20.0f;
typedef enum
{
TGMenuSheetAnimationChange,
TGMenuSheetAnimationDismiss,
TGMenuSheetAnimationPresent,
TGMenuSheetAnimationFastDismiss
} TGMenuSheetAnimation;
typedef enum
{
TGMenuPanDirectionHorizontal,
TGMenuPanDirectionVertical,
} TGMenuPanDirection;
@interface TGMenuPanGestureRecognizer : UIPanGestureRecognizer
@property (nonatomic, assign) TGMenuPanDirection direction;
@end
@interface TGMenuSheetContainerView : UIView
@end
@interface TGMenuSheetController () <UIGestureRecognizerDelegate, UIPopoverPresentationControllerDelegate, UIPopoverControllerDelegate, UIViewControllerPreviewingDelegate>
{
bool _dark;
UIView *_containerView;
TGMenuSheetDimView *_dimView;
UIImageView *_shadowView;
TGMenuSheetView *_sheetView;
bool _presented;
SMetaDisposable *_sizeClassDisposable;
UIUserInterfaceSizeClass _sizeClass;
bool _hasSwipeGesture;
TGMenuPanGestureRecognizer *_gestureRecognizer;
CGFloat _gestureStartPosition;
CGFloat _gestureActualStartPosition;
bool _shouldPassPanOffset;
bool _wasPanning;
bool _hasDistractableItems;
__weak UIView *_sourceView;
__weak UIViewController *_parentController;
CGFloat _keyboardOffset;
id _keyboardWillChangeFrameProxy;
bool _checked3dTouch;
NSDictionary *_3dTouchHandlers;
UIPopoverController *_popoverController;
id<LegacyComponentsContext> _context;
}
@end
@implementation TGMenuSheetController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context dark:(bool)dark
{
self = [super init];
if (self != nil)
{
_context = context;
_dark = dark;
_disposables = [[SDisposableSet alloc] init];
_permittedArrowDirections = UIPopoverArrowDirectionDown;
_requiuresDimView = true;
if (dark && [[LegacyComponentsGlobals provider] respondsToSelector:@selector(darkMenuSheetPallete)])
self.pallete = [[LegacyComponentsGlobals provider] darkMenuSheetPallete];
else if (!dark && [[LegacyComponentsGlobals provider] respondsToSelector:@selector(menuSheetPallete)])
self.pallete = [[LegacyComponentsGlobals provider] menuSheetPallete];
self.wantsFullScreenLayout = true;
}
return self;
}
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context itemViews:(NSArray *)itemViews
{
self = [self initWithContext:context dark:false];
if (self != nil)
{
[self setItemViews:itemViews];
}
return self;
}
- (void)dealloc
{
[_disposables dispose];
[_sizeClassDisposable dispose];
}
- (void)loadView
{
[super loadView];
self.view = [[TGMenuSheetContainerView alloc] initWithFrame:self.view.frame];
if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact || _forceFullScreen)
{
self.view.frame = [_context fullscreenBounds];
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
__weak TGMenuSheetController *weakSelf = self;
_sizeClassDisposable = [[SMetaDisposable alloc] init];
[_sizeClassDisposable setDisposable:[[_context sizeClassSignal] startWithNext:^(NSNumber *next)
{
__strong TGMenuSheetController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
UIUserInterfaceSizeClass sizeClass = next.integerValue;
[strongSelf updateTraitsWithSizeClass:sizeClass];
}]];
_containerView = [[TGMenuSheetContainerView alloc] initWithFrame:CGRectZero];
[self.view addSubview:_containerView];
if (self.requiuresDimView)
{
_dimView = [[TGMenuSheetDimView alloc] initWithActionMenuView:_sheetView];
_dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_dimView addTarget:self action:@selector(dimViewPressed) forControlEvents:UIControlEventTouchUpInside];
[_dimView setTheaterMode:_hasDistractableItems animated:false];
[_containerView addSubview:_dimView];
}
if (self.requiresShadow)
{
_shadowView = [[UIImageView alloc] init];
_shadowView.image = [TGComponentsImageNamed(@"PreviewSheetShadow") resizableImageWithCapInsets:UIEdgeInsetsMake(42.0f, 42.0f, 42.0f, 42.0f)];
[_containerView addSubview:_shadowView];
}
[_containerView addSubview:_sheetView];
_keyboardWillChangeFrameProxy = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification];
}
- (void)setRequiresShadow:(bool)requiresShadow
{
_requiresShadow = requiresShadow;
if (!_requiresShadow && _shadowView != nil)
{
[_shadowView removeFromSuperview];
_shadowView = nil;
}
}
- (void)setRequiuresDimView:(bool)requiuresDimView
{
_requiuresDimView = requiuresDimView;
if (_requiuresDimView && _itemViews.count > 0 && _containerView != nil)
{
_dimView = [[TGMenuSheetDimView alloc] initWithActionMenuView:_sheetView];
_dimView.alpha = 0.0f;
_dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_dimView addTarget:self action:@selector(dimViewPressed) forControlEvents:UIControlEventTouchUpInside];
[_dimView setTheaterMode:_hasDistractableItems animated:false];
[_containerView insertSubview:_dimView atIndex:0];
[UIView animateWithDuration:0.2 animations:^
{
_dimView.alpha = 1.0f;
}];
}
}
- (void)setItemViews:(NSArray *)itemViews
{
[self setItemViews:itemViews animated:false];
}
- (void)setItemViews:(NSArray *)itemViews animated:(bool)animated
{
UIUserInterfaceSizeClass sizeClass = [self sizeClass];
bool compact = (sizeClass == UIUserInterfaceSizeClassCompact);
bool hasDistractableItems = false;
for (TGMenuSheetItemView *itemView in itemViews)
{
itemView.menuController = self;
if (itemView.distractable)
hasDistractableItems = true;
}
_hasDistractableItems = hasDistractableItems;
if (_dimView != nil)
[_dimView setTheaterMode:_hasDistractableItems animated:animated];
__weak TGMenuSheetController *weakSelf = self;
void (^menuRelayout)(void) = ^
{
__strong TGMenuSheetController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf repositionMenuWithReferenceSize:[strongSelf->_context fullscreenBounds].size];
};
if (animated && (compact || _forceFullScreen))
{
TGMenuSheetView *sheetView = _sheetView;
UIView *snapshotView = [sheetView snapshotViewAfterScreenUpdates:false];
snapshotView.frame = [_containerView convertRect:sheetView.frame toView:_containerView.superview];
[_containerView.superview addSubview:snapshotView];
[sheetView menuWillDisappearAnimated:false];
[sheetView removeFromSuperview];
[sheetView menuDidDisappearAnimated:false];
void (^changeBlock)(void) = ^
{
snapshotView.frame = CGRectMake(snapshotView.frame.origin.x, snapshotView.frame.origin.y + snapshotView.frame.size.height, snapshotView.frame.size.width, snapshotView.frame.size.height);
};
void (^completionBlock)(BOOL) = ^(__unused BOOL finished)
{
[snapshotView removeFromSuperview];
};
if (iosMajorVersion() >= 7)
{
[UIView animateWithDuration:0.25 delay:0.0 usingSpringWithDamping:1.5 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:changeBlock completion:completionBlock];
}
else
{
[UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:changeBlock completion:completionBlock];
}
_sheetView = [[TGMenuSheetView alloc] initWithContext:_context pallete:_pallete itemViews:itemViews sizeClass:sizeClass dark:_dark borderless:_borderless];
_sheetView.menuRelayout = menuRelayout;
_sheetView.menuWidth = sheetView.menuWidth;
_sheetView.maxHeight = _maxHeight;
[_containerView addSubview:_sheetView];
[self updateGestureRecognizer];
[self.view setNeedsLayout];
[self applySheetOffset:_sheetView.menuHeight];
[_sheetView menuWillAppearAnimated:animated];
[self animateSheetViewToPosition:0 velocity:0 type:TGMenuSheetAnimationPresent completion:^
{
[_sheetView menuDidAppearAnimated:animated];
}];
}
else
{
void (^configureBlock)(void) = ^
{
_sheetView = [[TGMenuSheetView alloc] initWithContext:_context pallete:_pallete itemViews:itemViews sizeClass:sizeClass dark:_dark borderless:_borderless];
_sheetView.menuRelayout = menuRelayout;
_sheetView.maxHeight = _maxHeight;
if (self.isViewLoaded)
[_containerView addSubview:_sheetView];
[self updateGestureRecognizer];
[self.view setNeedsLayout];
};
if (_sheetView != nil)
{
[_parentController dismissViewControllerAnimated:false completion:^
{
[_sheetView menuWillDisappearAnimated:animated];
[_sheetView removeFromSuperview];
[_sheetView menuDidDisappearAnimated:animated];
configureBlock();
[_sheetView menuWillAppearAnimated:animated];
[self _presentPopoverInController:_parentController];
[_sheetView menuDidAppearAnimated:animated];
}];
}
else
{
configureBlock();
}
}
_itemViews = itemViews;
}
- (void)removeItemViewsAtIndexes:(NSIndexSet *)indexes
{
NSMutableArray *newItemViews = [_itemViews mutableCopy];
[newItemViews removeObjectsAtIndexes:indexes];
[_sheetView setItemViews:nil animated:true];
}
- (void)dimViewPressed
{
if (!self.dismissesByOutsideTap)
return;
bool dismissalAllowed = true;
if (_sheetView.tapDismissalAllowed != nil)
dismissalAllowed = _sheetView.tapDismissalAllowed();
if (!dismissalAllowed)
return;
[self dismissAnimated:true manual:true];
}
#pragma mark -
- (UIView *)sourceView
{
return _sourceView;
}
- (UIUserInterfaceSizeClass)sizeClass
{
UIUserInterfaceSizeClass sizeClass = _sizeClass;
if (self.inhibitPopoverPresentation)
sizeClass = UIUserInterfaceSizeClassCompact;
return sizeClass;
}
- (bool)isInPopover
{
if ([_parentController isKindOfClass:[TGNavigationController class]])
{
TGNavigationController *navController = (TGNavigationController *)_parentController;
if (navController.presentationStyle == TGNavigationControllerPresentationStyleRootInPopover)
return true;
}
return false;
}
#pragma mark -
- (void)popoverPresentationController:(UIPopoverPresentationController *)__unused popoverPresentationController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)__unused view
{
if (self.sourceRect != nil)
*rect = self.sourceRect();
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)__unused popoverController
{
_popoverController = nil;
}
- (void)popoverController:(UIPopoverController *)__unused popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)__unused view
{
if (self.sourceRect != nil)
*rect = self.sourceRect();
}
#pragma mark -
- (void)_presentPopoverInController:(UIViewController *)controller
{
if (_sourceView == nil && self.barButtonItem == nil)
return;
if (iosMajorVersion() >= 8)
{
[controller presentViewController:self animated:false completion:nil];
if (self.popoverPresentationController == nil)
return;
UIColor *backgroundColor = self.pallete != nil ? self.pallete.backgroundColor : [UIColor whiteColor];
self.popoverPresentationController.backgroundColor = _dark ? UIColorRGB(0x161616) : backgroundColor;
self.popoverPresentationController.delegate = self;
self.popoverPresentationController.permittedArrowDirections = self.permittedArrowDirections;
if (self.barButtonItem != nil)
{
self.popoverPresentationController.barButtonItem = self.barButtonItem;
}
else
{
self.popoverPresentationController.sourceView = _sourceView;
CGRect sourceRect = _sourceView.bounds;
if (self.sourceRect != nil)
sourceRect = self.sourceRect();
self.popoverPresentationController.sourceRect = sourceRect;
}
}
else
{
_popoverController = [[UIPopoverController alloc] initWithContentViewController:self];
UIColor *backgroundColor = self.pallete != nil ? self.pallete.backgroundColor : [UIColor whiteColor];
if ([_popoverController respondsToSelector:@selector(setBackgroundColor:)])
_popoverController.backgroundColor = _dark ? UIColorRGB(0x161616) : backgroundColor;
if (self.barButtonItem != nil)
{
[_popoverController presentPopoverFromBarButtonItem:self.barButtonItem permittedArrowDirections:self.permittedArrowDirections animated:false];
}
else
{
CGRect sourceRect = _sourceView.bounds;
if (self.sourceRect != nil)
sourceRect = self.sourceRect();
[_popoverController presentPopoverFromRect:sourceRect inView:self.sourceView permittedArrowDirections:self.permittedArrowDirections animated:false];
}
}
}
- (void)presentInViewController:(UIViewController *)viewController sourceView:(UIView *)sourceView animated:(bool)animated
{
_sourceView = sourceView;
UIUserInterfaceSizeClass sizeClass = [self sizeClass];
bool compact = (sizeClass == UIUserInterfaceSizeClassCompact);
if (compact || _forceFullScreen)
self.modalPresentationStyle = UIModalPresentationFullScreen;
else {
self.modalPresentationStyle = UIModalPresentationPopover;
}
if (!self.stickWithSpecifiedParentController && viewController.navigationController != nil)
viewController = viewController.navigationController.parentViewController ?: viewController.navigationController;
_parentController = viewController;
if ([_parentController.presentedViewController isKindOfClass:[TGMenuSheetController class]])
return;
for (UIViewController *controller in _parentController.childViewControllers)
{
if ([controller isKindOfClass:[TGMenuSheetController class]])
return;
}
if (sizeClass == UIUserInterfaceSizeClassRegular || [self isInPopover])
{
_sheetView.menuWidth = TGMenuSheetPadMenuWidth;
}
else
{
CGSize referenceSize = TGIsPad() ? (self.inhibitPopoverPresentation ? CGSizeMake(TGMenuSheetPadMenuWidth, viewController.view.bounds.size.height) : viewController.view.bounds.size) : [_context fullscreenBounds].size;
CGFloat minSide = MIN(referenceSize.width, referenceSize.height);
_sheetView.narrowInLandscape = self.narrowInLandscape;
if (self.narrowInLandscape)
_sheetView.menuWidth = minSide;
else
_sheetView.menuWidth = referenceSize.width;
}
if ((compact || _forceFullScreen) && !self.inhibitPopoverPresentation)
{
[viewController addChildViewController:self];
[viewController.view addSubview:self.view];
if (TGIsPad())
self.view.frame = viewController.view.bounds;
_dimView.alpha = 0.0f;
[self setDimViewHidden:false animated:animated];
if (iosMajorVersion() >= 7 && [viewController isKindOfClass:[TGNavigationController class]])
((TGNavigationController *)viewController).interactivePopGestureRecognizer.enabled = false;
if (animated)
{
CGFloat menuHeight = _sheetView.menuHeight;
[self applySheetOffset:menuHeight];
if (self.willPresent != nil)
{
[self viewWillLayoutSubviews];
self.willPresent(menuHeight);
}
[self viewWillLayoutSubviews];
[_sheetView menuWillAppearAnimated:animated];
[self animateSheetViewToPosition:0 velocity:0 type:TGMenuSheetAnimationPresent completion:^
{
[_sheetView menuDidAppearAnimated:animated];
_presented = true;
}];
}
else
{
if (self.willPresent != nil)
self.willPresent(0);
[_sheetView menuWillAppearAnimated:animated];
[_sheetView menuDidAppearAnimated:animated];
_presented = true;
}
}
else
{
[_sheetView menuSize];
if (self.willPresent != nil)
self.willPresent(0);
[_sheetView menuWillAppearAnimated:false];
[self _presentPopoverInController:viewController];
[_sheetView menuDidAppearAnimated:false];
_presented = true;
}
[self setup3DTouch];
}
- (void)dismissAnimated:(bool)animated
{
[self dismissAnimated:animated manual:false];
}
- (void)dismissAnimated:(bool)animated manual:(bool)manual
{
[self dismissAnimated:animated manual:manual completion:nil];
}
- (void)dismissAnimated:(bool)animated manual:(bool)manual completion:(void (^)(void))completion
{
if (_ignoreNextDismissal)
{
_ignoreNextDismissal = false;
return;
}
bool compact = ([self sizeClass] == UIUserInterfaceSizeClassCompact);
if (self.willDismiss != nil)
self.willDismiss(manual);
if (compact || _forceFullScreen)
{
if (iosMajorVersion() >= 7 && [self.parentViewController isKindOfClass:[TGNavigationController class]])
((TGNavigationController *)self.parentViewController).interactivePopGestureRecognizer.enabled = true;
[_sheetView menuWillDisappearAnimated:animated];
[self setDimViewHidden:true animated:animated];
if (animated)
{
self.view.userInteractionEnabled = false;
[self animateSheetViewToPosition:_sheetView.menuHeight + [self safeAreaInsetForOrientation:self.interfaceOrientation].bottom velocity:0 type:TGMenuSheetAnimationDismiss completion:^
{
[self.view removeFromSuperview];
[self removeFromParentViewController];
[_sheetView menuDidDisappearAnimated:animated];
if (self.didDismiss != nil)
self.didDismiss(manual);
if (completion != nil)
completion();
}];
}
else
{
[self.view removeFromSuperview];
[self removeFromParentViewController];
[_sheetView menuDidDisappearAnimated:animated];
if (self.didDismiss != nil)
self.didDismiss(manual);
if (completion != nil)
completion();
}
}
else
{
[_sheetView menuWillDisappearAnimated:animated];
void (^dismissedBlock)(void) = ^
{
[_sheetView menuDidDisappearAnimated:animated];
if (self.didDismiss != nil)
self.didDismiss(manual);
if (completion != nil)
completion();
if ([self.parentViewController isKindOfClass:[TGOverlayController class]]) {
TGOverlayControllerWindow *window = ((TGOverlayController *)self.parentViewController).overlayWindow;
if (window.dismissByMenuSheet) {
[window dismiss];
}
}
};
if (_popoverController == nil)
{
[self.presentingViewController dismissViewControllerAnimated:false completion:dismissedBlock];
}
else
{
[_popoverController dismissPopoverAnimated:false];
dismissedBlock();
}
}
}
- (void)animateSheetViewToPosition:(CGFloat)position velocity:(CGFloat)velocity type:(TGMenuSheetAnimation)type completion:(void (^)(void))completion
{
CGFloat animationVelocity = position > 0 ? fabs(velocity) / fabs(position - self.view.frame.origin.y) : 0;
void (^changeBlock)(void) = ^
{
_containerView.frame = CGRectMake(_containerView.frame.origin.x, position, _containerView.frame.size.width, _containerView.frame.size.height);
[_sheetView didChangeAbsoluteFrame];
};
void (^completionBlock)(BOOL) = ^(__unused BOOL finished)
{
if (completion != nil)
completion();
};
if (type == TGMenuSheetAnimationPresent)
{
UIViewAnimationOptions options = UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionAllowAnimatedContent;
if (self.borderless && iosMajorVersion() >= 7)
{
[UIView animateWithDuration:0.45 delay:0.0 usingSpringWithDamping:0.8f initialSpringVelocity:0.2 options:options animations:changeBlock completion:completionBlock];
}
else
{
if (iosMajorVersion() >= 7)
options |= 7 << 16;
[UIView animateWithDuration:0.3 delay:0.0 options:options animations:changeBlock completion:completionBlock];
}
}
else
{
CGFloat duration = 0.25;
if (type == TGMenuSheetAnimationFastDismiss)
duration = 0.2;
if (iosMajorVersion() >= 7)
{
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:1.5 initialSpringVelocity:animationVelocity options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowAnimatedContent animations:changeBlock completion:completionBlock];
}
else
{
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowAnimatedContent animations:changeBlock completion:completionBlock];
}
}
}
#pragma mark -
- (bool)hasSwipeGesture
{
return _hasSwipeGesture;
}
- (void)setHasSwipeGesture:(bool)hasSwipeGesture
{
if (_hasSwipeGesture == hasSwipeGesture)
return;
_hasSwipeGesture = hasSwipeGesture;
[self updateGestureRecognizer];
}
- (void)updateGestureRecognizer
{
if (_sheetView == nil)
return;
if (_hasSwipeGesture && ([self sizeClass] != UIUserInterfaceSizeClassRegular || _forceFullScreen))
{
if (_gestureRecognizer != nil)
{
[_sheetView removeGestureRecognizer:_gestureRecognizer];
_gestureRecognizer = nil;
}
_gestureRecognizer = [[TGMenuPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
_gestureRecognizer.direction = TGMenuPanDirectionVertical;
_gestureRecognizer.delegate = self;
[_sheetView addGestureRecognizer:_gestureRecognizer];
__weak TGMenuSheetController *weakSelf = self;
_sheetView.handleInternalPan = ^(UIPanGestureRecognizer *gestureRecognizer)
{
__strong TGMenuSheetController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf handlePan:gestureRecognizer];
};
}
else
{
[_sheetView removeGestureRecognizer:_gestureRecognizer];
_gestureRecognizer = nil;
_sheetView.handleInternalPan = nil;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer
{
for (TGMenuSheetItemView *itemView in _sheetView.itemViews)
{
if ([itemView inhibitPan])
return false;
}
return true;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer == _gestureRecognizer)
{
if ([otherGestureRecognizer.view isKindOfClass:[TGMenuSheetCollectionView class]])
{
TGMenuSheetCollectionView *collectionView = (TGMenuSheetCollectionView *)otherGestureRecognizer.view;
return collectionView.allowSimultaneousPan;
}
else if ([otherGestureRecognizer.view isKindOfClass:[TGMenuSheetScrollView class]])
{
return true;
}
return false;
}
return false;
}
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer
{
CGFloat location = [gestureRecognizer locationInView:self.view].y;
CGFloat offset = location - _gestureStartPosition;
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
{
_gestureStartPosition = location;
_gestureActualStartPosition = location;
CGRect activeRect = [_sheetView activePanRect];
_shouldPassPanOffset = !CGRectIsNull(activeRect) && (CGRectContainsPoint(activeRect, CGPointMake(self.view.frame.size.width / 2.0f, _gestureStartPosition)));
}
break;
case UIGestureRecognizerStateChanged:
{
bool shouldPan = _shouldPassPanOffset && [_sheetView passPanOffset:offset];
if (!shouldPan)
{
_wasPanning = false;
[self applySheetOffset:0];
}
else
{
if (!_wasPanning)
{
_gestureStartPosition = location;
_wasPanning = true;
offset = 0;
}
}
if (!_shouldPassPanOffset || shouldPan)
[self applySheetOffset:[self swipeOffsetForOffset:offset]];
}
break;
case UIGestureRecognizerStateEnded:
{
CGFloat velocity = [gestureRecognizer velocityInView:self.view].y;
bool allowDismissal = !_shouldPassPanOffset || _wasPanning;
if (velocity > 200.0f && allowDismissal)
{
[self setDimViewHidden:true animated:true];
[self animateSheetViewToPosition:_sheetView.menuHeight + [self safeAreaInsetForOrientation:self.interfaceOrientation].bottom velocity:velocity type:TGMenuSheetAnimationDismiss completion:^
{
[self dismissAnimated:false];
}];
}
else
{
[self animateSheetViewToPosition:0 velocity:0 type:TGMenuSheetAnimationChange completion:nil];
}
}
break;
case UIGestureRecognizerStateCancelled:
{
[self animateSheetViewToPosition:0 velocity:0 type:TGMenuSheetAnimationChange completion:nil];
}
break;
default:
break;
}
}
- (void)applySheetOffset:(CGFloat)offset
{
_containerView.frame = CGRectMake(_containerView.frame.origin.x, self.view.frame.origin.y + offset, self.view.frame.size.width, self.view.frame.size.height);
[_sheetView didChangeAbsoluteFrame];
}
- (CGFloat)swipeOffsetForOffset:(CGFloat)offset
{
if (offset >= 0)
return offset;
static CGFloat c = 0.1f;
static CGFloat d = 300.0f;
return (1.0f - (1.0f / ((offset * c / d) + 1.0f))) * d;
}
- (CGFloat)clampVelocity:(CGFloat)velocity
{
CGFloat value = velocity < 0.0f ? -velocity : velocity;
value = MIN(30.0f, 0.0f);
return velocity < 0.0f ? -value : value;
}
#pragma mark - Traits
- (void)updateTraitsWithSizeClass:(UIUserInterfaceSizeClass)sizeClass
{
UIUserInterfaceSizeClass previousClass = [self sizeClass];
_sizeClass = sizeClass;
[_sheetView updateTraitsWithSizeClass:_forceFullScreen ? UIUserInterfaceSizeClassCompact : [self sizeClass]];
if (_presented && previousClass != [self sizeClass])
{
if (sizeClass == UIUserInterfaceSizeClassRegular && !_forceFullScreen) {
_dimView.hidden = true;
self.modalPresentationStyle = UIModalPresentationPopover;
[self.view removeFromSuperview];
[self removeFromParentViewController];
[self _presentPopoverInController:_parentController];
if (iosMajorVersion() >= 7 && [_parentController isKindOfClass:[TGNavigationController class]])
((TGNavigationController *)_parentController).interactivePopGestureRecognizer.enabled = true;
} else {
_dimView.hidden = false;
[self.presentingViewController dismissViewControllerAnimated:false completion:^
{
self.modalPresentationStyle = UIModalPresentationFullScreen;
[_parentController addChildViewController:self];
[_parentController.view addSubview:self.view];
[self.view setNeedsLayout];
if (iosMajorVersion() >= 7 && [_parentController isKindOfClass:[TGNavigationController class]])
((TGNavigationController *)_parentController).interactivePopGestureRecognizer.enabled = false;
}];
}
}
[self updateGestureRecognizer];
}
#pragma mark -
- (void)viewWillLayoutSubviews
{
if (([self sizeClass] == UIUserInterfaceSizeClassRegular || [self isInPopover]) && !_forceFullScreen)
{
_sheetView.menuWidth = TGMenuSheetPadMenuWidth;
CGSize menuSize = _sheetView.menuSize;
if (iosMajorVersion() >= 7)
self.preferredContentSize = menuSize;
_sheetView.frame = CGRectMake(0, 0, menuSize.width, self.view.frame.size.height);
_containerView.frame = _sheetView.bounds;
_dimView.frame = CGRectZero;
}
else
{
CGSize referenceSize = TGIsPad() ? _parentController.view.bounds.size : [_context fullscreenBounds].size;
CGFloat viewWidth = self.view.frame.size.width;
if ([self sizeClass] == UIUserInterfaceSizeClassRegular) {
referenceSize.width = TGMenuSheetPadMenuWidth;
}
_containerView.frame = CGRectMake(_containerView.frame.origin.x, _containerView.frame.origin.y, viewWidth, self.view.frame.size.height);
_dimView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
_sheetView.safeAreaInset = [self safeAreaInsetForOrientation:self.interfaceOrientation];
CGFloat minSide = MIN(referenceSize.width, referenceSize.height);
_sheetView.narrowInLandscape = self.narrowInLandscape;
if (self.narrowInLandscape)
_sheetView.menuWidth = minSide;
else
_sheetView.menuWidth = referenceSize.width;
[_sheetView layoutSubviews];
[self repositionMenuWithReferenceSize:referenceSize];
}
[_sheetView didChangeAbsoluteFrame];
}
- (UIEdgeInsets)safeAreaInsetForOrientation:(UIInterfaceOrientation)orientation
{
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
if (safeAreaInset.bottom > FLT_EPSILON)
safeAreaInset.bottom -= 12.0f;
return safeAreaInset;
}
- (UIEdgeInsets)safeAreaInset
{
return [self safeAreaInsetForOrientation:self.interfaceOrientation];
}
- (void)repositionMenuWithReferenceSize:(CGSize)referenceSize
{
if ([self sizeClass] == UIUserInterfaceSizeClassRegular && !_forceFullScreen)
return;
UIEdgeInsets safeAreaInset = [self safeAreaInsetForOrientation:self.interfaceOrientation];
if (_keyboardOffset > FLT_EPSILON)
safeAreaInset.bottom = 0.0f;
CGFloat defaultStatusBarHeight = TGMenuSheetDefaultStatusBarHeight;
if (!TGIsPad() && iosMajorVersion() >= 11 && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
defaultStatusBarHeight = 0.0;
CGFloat statusBarHeight = !UIEdgeInsetsEqualToEdgeInsets(safeAreaInset, UIEdgeInsetsZero) ? safeAreaInset.top : defaultStatusBarHeight;
referenceSize.height = referenceSize.height + statusBarHeight - [self statusBarHeight];
CGSize menuSize = _sheetView.menuSize;
_sheetView.frame = CGRectMake((_containerView.frame.size.width - menuSize.width) / 2.0f, referenceSize.height - menuSize.height - safeAreaInset.bottom, menuSize.width, menuSize.height);
_shadowView.frame = CGRectInset([self _shadowFrame], safeAreaInset.left, 0.0f);
}
- (CGRect)_shadowFrame
{
CGRect frame = _sheetView.frame;
frame.origin.x -= 6.5f;
frame.size.width += 13.0f;
frame.origin.y -= 6.0f;
frame.size.height += 13.0f;
return frame;
}
- (CGFloat)statusBarHeight
{
CGSize statusBarSize = [_context statusBarFrame].size;
CGFloat statusBarHeight = MIN(statusBarSize.width, statusBarSize.height);
statusBarHeight = MAX(TGMenuSheetDefaultStatusBarHeight, statusBarHeight);
if (!TGIsPad() && iosMajorVersion() >= 11 && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
return 0.0f;
}
else
{
UIEdgeInsets safeAreaInset = [self safeAreaInsetForOrientation:self.interfaceOrientation];
if (!UIEdgeInsetsEqualToEdgeInsets(safeAreaInset, UIEdgeInsetsZero))
statusBarHeight = 44.0f;
}
return statusBarHeight;
}
- (CGFloat)menuHeight
{
return _sheetView.menuHeight;
}
- (void)setDimViewHidden:(bool)hidden animated:(bool)animated
{
void (^changeBlock)(void) = ^
{
_dimView.alpha = hidden ? 0.0f : 1.0f;
};
if (animated)
[UIView animateWithDuration:0.25f animations:changeBlock];
else
changeBlock();
}
- (UIViewController *)parentController
{
return _parentController;
}
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
NSTimeInterval duration = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] == nil ? 0.3 : [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
int curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
CGRect screenKeyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect keyboardFrame = [self.view convertRect:screenKeyboardFrame fromView:nil];
CGFloat keyboardHeight = (keyboardFrame.size.height <= FLT_EPSILON || keyboardFrame.size.width <= FLT_EPSILON) ? 0.0f : (self.view.frame.size.height - keyboardFrame.origin.y);
keyboardHeight = MAX(keyboardHeight, 0.0f);
if (self.followsKeyboard)
{
if (duration >= FLT_EPSILON)
{
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | curve animations:^{
[self updateKeyboardOffset:keyboardHeight];
} completion:nil];
}
else
{
[self updateKeyboardOffset:keyboardHeight];
}
}
}
- (void)updateKeyboardOffset:(CGFloat)keyboardOffset
{
_keyboardOffset = keyboardOffset;
_sheetView.keyboardOffset = keyboardOffset;
[self repositionMenuWithReferenceSize:[_context fullscreenBounds].size];
[_sheetView layoutSubviews];
}
- (void)setMaxHeight:(CGFloat)maxHeight
{
_maxHeight = maxHeight;
_sheetView.maxHeight = maxHeight;
}
- (void)removeFromParentViewController {
if ([self.parentViewController isKindOfClass:[TGOverlayController class]]) {
TGOverlayControllerWindow *window = ((TGOverlayController *)self.parentViewController).overlayWindow;
if (window.dismissByMenuSheet) {
[window dismiss];
}
}
if (_customRemoveFromParentViewController) {
_customRemoveFromParentViewController();
}
[super removeFromParentViewController];
}
#pragma mark -
- (void)setup3DTouch
{
if (iosMajorVersion() >= 9 && self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)
{
for (TGMenuSheetItemView *itemView in _sheetView.itemViews)
{
if (itemView.previewSourceView != nil)
[self registerForPreviewingWithDelegate:itemView sourceView:itemView.previewSourceView];
}
}
}
- (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)__unused popoverPresentationController {
if ([self.parentViewController isKindOfClass:[TGOverlayController class]]) {
TGOverlayControllerWindow *window = ((TGOverlayController *)self.parentViewController).overlayWindow;
if (window.dismissByMenuSheet) {
[window dismiss];
}
} else if ([self.parentController isKindOfClass:[TGOverlayController class]]) {
TGOverlayControllerWindow *window = ((TGOverlayController *)self.parentController).overlayWindow;
if (window.dismissByMenuSheet) {
[window dismiss];
}
}
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
_sheetView.safeAreaInset = [self safeAreaInsetForOrientation:toInterfaceOrientation];
for (TGMenuSheetItemView *itemView in _sheetView.itemViews)
{
[itemView _willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)__unused fromInterfaceOrientation
{
for (TGMenuSheetItemView *itemView in _sheetView.itemViews)
{
[itemView _didRotateToInterfaceOrientation:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]];
}
}
@end
@implementation TGMenuPanGestureRecognizer
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (self.state != UIGestureRecognizerStateBegan)
return;
CGPoint velocity = [self velocityInView:self.view];
switch (self.direction)
{
case TGMenuPanDirectionHorizontal:
if (fabs(velocity.y) > fabs(velocity.x))
self.state = UIGestureRecognizerStateCancelled;
break;
case TGMenuPanDirectionVertical:
if (fabs(velocity.x) > fabs(velocity.y))
self.state = UIGestureRecognizerStateCancelled;
break;
default:
break;
}
}
@end
@implementation TGMenuSheetContainerView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if (view == self)
return nil;
return view;
}
@end
@implementation TGMenuSheetPallete
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor spinnerColor:(UIColor *)spinnerColor badgeTextColor:(UIColor *)badgeTextColor badgeImage:(UIImage *)badgeImage cornersImage:(UIImage *)cornersImage
{
TGMenuSheetPallete *pallete = [[TGMenuSheetPallete alloc] init];
pallete->_isDark = dark;
pallete->_backgroundColor = backgroundColor;
pallete->_selectionColor = selectionColor;
pallete->_separatorColor = separatorColor;
pallete->_accentColor = accentColor;
pallete->_destructiveColor = destructiveColor;
pallete->_textColor = textColor;
pallete->_secondaryTextColor = secondaryTextColor;
pallete->_spinnerColor = spinnerColor;
pallete->_badgeTextColor = badgeTextColor;
pallete->_badgeImage = badgeImage;
pallete->_cornersImage = cornersImage;
return pallete;
}
@end