mirror of
https://github.com/danog/Telegram.git
synced 2024-12-02 09:27:55 +01:00
1464 lines
53 KiB
Objective-C
1464 lines
53 KiB
Objective-C
/*
|
|
* This is the source code of Telegram for iOS v. 1.1
|
|
* It is licensed under GNU GPL v. 2 or later.
|
|
* You should have received a copy of the license in this archive (see LICENSE).
|
|
*
|
|
* Copyright Peter Iakovlev, 2013.
|
|
*/
|
|
|
|
#import "TGModernGalleryController.h"
|
|
|
|
#import "TGHacks.h"
|
|
|
|
#import "TGModernGalleryView.h"
|
|
|
|
#import "TGAppDelegate.h"
|
|
|
|
#import "TGModernGalleryItem.h"
|
|
#import "TGModernGalleryScrollView.h"
|
|
#import "TGModernGalleryItemView.h"
|
|
#import "TGModernGalleryTransitionView.h"
|
|
|
|
#import "TGModernGalleryContainerView.h"
|
|
#import "TGModernGalleryInterfaceView.h"
|
|
#import "TGModernGalleryDefaultInterfaceView.h"
|
|
|
|
#import "TGModernGalleryModel.h"
|
|
|
|
#import "TGAnimationBlockDelegate.h"
|
|
|
|
#import <pop/POP.h>
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import "JNWSpringAnimation.h"
|
|
|
|
#import "TGImageUtils.h"
|
|
|
|
#import "TGSharedMediaCollectionView.h"
|
|
|
|
#define TGModernGalleryItemPadding 20.0f
|
|
|
|
@interface TGModernGalleryController () <UIScrollViewDelegate, TGModernGalleryScrollViewDelegate, TGModernGalleryItemViewDelegate, TGKeyCommandResponder>
|
|
{
|
|
NSMutableDictionary *_reusableItemViewsByIdentifier;
|
|
NSMutableArray *_visibleItemViews;
|
|
bool _preloadVisibleItemViews;
|
|
|
|
TGModernGalleryView *_view;
|
|
UIView<TGModernGalleryDefaultHeaderView> *_defaultHeaderView;
|
|
UIView<TGModernGalleryDefaultFooterView> *_defaultFooterView;
|
|
|
|
NSUInteger _lastReportedFocusedIndex;
|
|
bool _synchronousBoundsChange;
|
|
bool _reloadingItems;
|
|
bool _isBeingDismissed;
|
|
|
|
UIStatusBarStyle _statusBarStyle;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation TGModernGalleryController
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self != nil)
|
|
{
|
|
self.automaticallyManageScrollViewInsets = false;
|
|
self.autoManageStatusBarBackground = false;
|
|
_lastReportedFocusedIndex = NSNotFound;
|
|
_statusBarStyle = UIStatusBarStyleLightContent;
|
|
_animateTransition = true;
|
|
_showInterface = true;
|
|
_adjustsStatusBarVisibility = true;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
_view.scrollView.delegate = nil;
|
|
_view.scrollView.scrollDelegate = nil;
|
|
}
|
|
|
|
- (void)dismiss
|
|
{
|
|
[super dismiss];
|
|
|
|
if (_completedTransitionOut)
|
|
_completedTransitionOut();
|
|
}
|
|
|
|
- (BOOL)prefersStatusBarHidden
|
|
{
|
|
if (self.childViewControllers.count > 0)
|
|
return [self.childViewControllers.lastObject prefersStatusBarHidden];
|
|
|
|
return [super prefersStatusBarHidden];
|
|
}
|
|
|
|
- (UIStatusBarStyle)preferredStatusBarStyle
|
|
{
|
|
return _statusBarStyle;
|
|
}
|
|
|
|
- (BOOL)shouldAutorotate
|
|
{
|
|
return [super shouldAutorotate] && (_view == nil || [_view shouldAutorotate]) && ([_model _shouldAutorotate]);
|
|
}
|
|
|
|
- (void)dismissWhenReady
|
|
{
|
|
/*for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
if (itemView.index == [self currentItemIndex])
|
|
{
|
|
if ([itemView dismissControllerNowOrSchedule])
|
|
[self dismiss];
|
|
|
|
return;
|
|
}
|
|
}*/
|
|
|
|
[self dismiss];
|
|
}
|
|
|
|
- (bool)isFullyOpaque
|
|
{
|
|
CGFloat alpha = 0.0f;
|
|
[_view.backgroundColor getWhite:NULL alpha:&alpha];
|
|
return alpha >= 1.0f - FLT_EPSILON;
|
|
}
|
|
|
|
- (void)setModel:(TGModernGalleryModel *)model
|
|
{
|
|
if (_model != model)
|
|
{
|
|
_model = model;
|
|
|
|
__weak TGModernGalleryController *weakSelf = self;
|
|
_model.itemsUpdated = ^(id<TGModernGalleryItem> item)
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
if (strongSelf != nil && !strongSelf->_isBeingDismissed)
|
|
[strongSelf reloadDataAtItem:item synchronously:false];
|
|
};
|
|
|
|
_model.focusOnItem = ^(id<TGModernGalleryItem> item)
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
NSUInteger index = [strongSelf.model.items indexOfObject:item];
|
|
[strongSelf setCurrentItemIndex:index == NSNotFound ? 0 : index synchronously:false];
|
|
};
|
|
|
|
_model.actionSheetView = ^
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
return strongSelf.view;
|
|
};
|
|
|
|
_model.viewControllerForModalPresentation = ^
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
return strongSelf;
|
|
};
|
|
|
|
_model.visibleItems = ^NSArray *()
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
return [strongSelf visibleItems];
|
|
};
|
|
|
|
_model.dismiss = ^(bool animated, bool asModal)
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
strongSelf->_isBeingDismissed = true;
|
|
|
|
if (asModal)
|
|
{
|
|
strongSelf->_statusBarStyle = UIStatusBarStyleDefault;
|
|
strongSelf.view.hidden = true;
|
|
|
|
if (strongSelf.completedTransitionOut)
|
|
strongSelf.completedTransitionOut();
|
|
|
|
[strongSelf dismissViewControllerAnimated:true completion:^
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), ^
|
|
{
|
|
[strongSelf dismiss];
|
|
});
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
if (animated)
|
|
{
|
|
if (iosMajorVersion() >= 7)
|
|
{
|
|
[strongSelf animateStatusBarTransition:0.2];
|
|
strongSelf->_statusBarStyle = UIStatusBarStyleDefault;
|
|
[strongSelf setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
if (strongSelf.adjustsStatusBarVisibility)
|
|
{
|
|
[UIView animateWithDuration:0.2 animations:^
|
|
{
|
|
[TGHacks setApplicationStatusBarAlpha:1.0f];
|
|
}];
|
|
}
|
|
|
|
[strongSelf->_view simpleTransitionOutWithVelocity:0.0f completion:^
|
|
{
|
|
[strongSelf dismiss];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
if (iosMajorVersion() >= 7)
|
|
{
|
|
strongSelf->_statusBarStyle = UIStatusBarStyleDefault;
|
|
[strongSelf setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
[strongSelf dismiss];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
_model.dismissWhenReady = ^
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
[strongSelf dismissWhenReady];
|
|
}
|
|
};
|
|
|
|
[self reloadDataAtItem:_model.focusItem synchronously:false];
|
|
}
|
|
}
|
|
|
|
- (void)itemViewIsReadyForScheduledDismiss:(TGModernGalleryItemView *)__unused itemView
|
|
{
|
|
[self dismiss];
|
|
}
|
|
|
|
- (void)itemViewDidRequestInterfaceShowHide:(TGModernGalleryItemView *)__unused itemView
|
|
{
|
|
[_view showHideInterface];
|
|
}
|
|
|
|
- (TGModernGalleryItemView *)dequeueViewForItem:(id<TGModernGalleryItem>)item
|
|
{
|
|
if (item == nil || [item viewClass] == nil)
|
|
return nil;
|
|
|
|
NSString *identifier = NSStringFromClass([item viewClass]);
|
|
NSMutableArray *views = _reusableItemViewsByIdentifier[identifier];
|
|
if (views == nil)
|
|
{
|
|
views = [[NSMutableArray alloc] init];
|
|
_reusableItemViewsByIdentifier[identifier] = views;
|
|
}
|
|
|
|
if (views.count == 0)
|
|
{
|
|
Class itemClass = [item viewClass];
|
|
TGModernGalleryItemView *itemView = [[itemClass alloc] init];
|
|
itemView.delegate = self;
|
|
itemView.defaultFooterView = _defaultFooterView;
|
|
itemView.defaultFooterAccessoryLeftView = [_model createDefaultLeftAccessoryView];
|
|
itemView.defaultFooterAccessoryRightView = [_model createDefaultRightAccessoryView];
|
|
|
|
return itemView;
|
|
}
|
|
|
|
TGModernGalleryItemView *itemView = [views lastObject];
|
|
[views removeLastObject];
|
|
|
|
itemView.delegate = self;
|
|
[itemView prepareForReuse];
|
|
return itemView;
|
|
}
|
|
|
|
- (void)enqueueView:(TGModernGalleryItemView *)itemView
|
|
{
|
|
if (itemView == nil)
|
|
return;
|
|
|
|
itemView.delegate = nil;
|
|
[itemView prepareForRecycle];
|
|
|
|
NSString *identifier = NSStringFromClass([itemView class]);
|
|
if (identifier != nil)
|
|
{
|
|
NSMutableArray *views = _reusableItemViewsByIdentifier[identifier];
|
|
if (views == nil)
|
|
{
|
|
views = [[NSMutableArray alloc] init];
|
|
_reusableItemViewsByIdentifier[identifier] = views;
|
|
}
|
|
[views addObject:itemView];
|
|
}
|
|
}
|
|
|
|
- (NSArray *)visibleItemViews
|
|
{
|
|
return _visibleItemViews;
|
|
}
|
|
|
|
- (TGModernGalleryItemView *)itemViewForItem:(id<TGModernGalleryItem>)item
|
|
{
|
|
for (TGModernGalleryItemView *itemView in self->_visibleItemViews)
|
|
{
|
|
if ([itemView.item isEqual:item])
|
|
{
|
|
return itemView;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (void)setShowInterface:(bool)showInterface
|
|
{
|
|
_showInterface = showInterface;
|
|
|
|
_view.userInteractionEnabled = showInterface;
|
|
|
|
_statusBarStyle = _showInterface ? UIStatusBarStyleLightContent : UIStatusBarStyleDefault;
|
|
}
|
|
|
|
- (void)loadView
|
|
{
|
|
[super loadView];
|
|
object_setClass(self.view, [TGModernGalleryContainerView class]);
|
|
|
|
self.view.frame = (CGRect){self.view.frame.origin, TGAppDelegateInstance.rootController.applicationBounds.size};
|
|
|
|
_reusableItemViewsByIdentifier = [[NSMutableDictionary alloc] init];
|
|
_visibleItemViews = [[NSMutableArray alloc] init];
|
|
|
|
UIView<TGModernGalleryInterfaceView> *interfaceView = [_model createInterfaceView];
|
|
if (interfaceView == nil)
|
|
interfaceView = [[TGModernGalleryDefaultInterfaceView alloc] initWithFrame:CGRectZero];
|
|
|
|
_view = [[TGModernGalleryView alloc] initWithFrame:self.view.bounds itemPadding:TGModernGalleryItemPadding interfaceView:interfaceView];
|
|
_view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self.view addSubview:_view];
|
|
|
|
_defaultHeaderView = [_model createDefaultHeaderView];
|
|
if (_defaultHeaderView != nil)
|
|
[_view addItemHeaderView:_defaultHeaderView];
|
|
|
|
_defaultFooterView = [_model createDefaultFooterView];
|
|
if (_defaultFooterView != nil)
|
|
[_view addItemFooterView:_defaultFooterView];
|
|
|
|
_view.scrollView.scrollDelegate = self;
|
|
_view.scrollView.delegate = self;
|
|
|
|
_view.userInteractionEnabled = _showInterface;
|
|
|
|
__weak TGModernGalleryController *weakSelf = self;
|
|
_view.transitionOut = ^bool (CGFloat velocity)
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
strongSelf->_isBeingDismissed = true;
|
|
|
|
id<TGModernGalleryItem> focusItem = nil;
|
|
if ([strongSelf currentItemIndex] < strongSelf.model.items.count)
|
|
focusItem = strongSelf.model.items[[strongSelf currentItemIndex]];
|
|
|
|
TGModernGalleryItemView *currentItemView = nil;
|
|
for (TGModernGalleryItemView *itemView in strongSelf->_visibleItemViews)
|
|
{
|
|
if ([itemView.item isEqual:focusItem])
|
|
{
|
|
currentItemView = itemView;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (strongSelf.hasFadeOutTransition)
|
|
{
|
|
if (strongSelf.beginTransitionOut)
|
|
strongSelf.beginTransitionOut(focusItem, currentItemView);
|
|
|
|
[strongSelf->_view fadeOutWithDuration:0.3f completion:^
|
|
{
|
|
[strongSelf dismiss];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
UIView *transitionOutToView = nil;
|
|
UIView *transitionOutFromView = nil;
|
|
CGRect transitionOutFromViewContentRect = CGRectZero;
|
|
|
|
if (strongSelf.beginTransitionOut && focusItem != nil)
|
|
transitionOutToView = strongSelf.beginTransitionOut(focusItem, currentItemView);
|
|
if (transitionOutToView != nil && currentItemView != nil)
|
|
{
|
|
transitionOutFromView = [currentItemView transitionView];
|
|
transitionOutFromViewContentRect = [currentItemView transitionViewContentRect];
|
|
}
|
|
|
|
if (transitionOutFromView != nil && transitionOutToView != nil)
|
|
{
|
|
[strongSelf animateTransitionOutFromView:transitionOutFromView fromViewContentRect:transitionOutFromViewContentRect toView:transitionOutToView velocity:CGPointMake(0.0f, velocity * 3.8f)];
|
|
[strongSelf->_view transitionOutWithDuration:0.15];
|
|
[strongSelf->_view.interfaceView animateTransitionOutWithDuration:0.15];
|
|
}
|
|
else
|
|
{
|
|
if (iosMajorVersion() >= 7)
|
|
{
|
|
[strongSelf animateStatusBarTransition:0.2];
|
|
strongSelf->_statusBarStyle = UIStatusBarStyleDefault;
|
|
[strongSelf setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
if (strongSelf.adjustsStatusBarVisibility)
|
|
{
|
|
[UIView animateWithDuration:0.2 animations:^
|
|
{
|
|
[TGHacks setApplicationStatusBarAlpha:1.0f];
|
|
}];
|
|
}
|
|
|
|
[strongSelf->_view simpleTransitionOutWithVelocity:velocity completion:^
|
|
{
|
|
__strong TGModernGalleryController *strongSelf2 = weakSelf;
|
|
[strongSelf2 dismiss];
|
|
}];
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
[self reloadDataAtItem:_model.focusItem synchronously:true];
|
|
|
|
if (_animateTransition)
|
|
{
|
|
UIView *transitionInFromView = nil;
|
|
UIView *transitionInToView = nil;
|
|
CGRect transitionInToViewContentRect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
|
|
if (_beginTransitionIn && _model.focusItem != nil)
|
|
{
|
|
TGModernGalleryItemView *itemView = nil;
|
|
for (TGModernGalleryItemView *visibleItemView in self->_visibleItemViews)
|
|
{
|
|
if ([visibleItemView.item isEqual:self.model.focusItem])
|
|
{
|
|
itemView = visibleItemView;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
transitionInFromView = _beginTransitionIn(_model.focusItem, itemView);
|
|
}
|
|
if (transitionInFromView != nil)
|
|
{
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
if ([itemView.item isEqual:_model.focusItem])
|
|
{
|
|
transitionInToView = [itemView transitionView];
|
|
transitionInToViewContentRect = [itemView transitionViewContentRect];
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (transitionInFromView != nil && transitionInToView != nil && transitionInToView.frame.size.width > FLT_EPSILON && transitionInToView.frame.size.height > FLT_EPSILON && transitionInToViewContentRect.size.width > FLT_EPSILON && transitionInToViewContentRect.size.height > FLT_EPSILON)
|
|
{
|
|
[self animateTransitionInFromView:transitionInFromView toView:transitionInToView toViewContentRect:transitionInToViewContentRect];
|
|
}
|
|
else if (_finishedTransitionIn && _model.focusItem != nil)
|
|
{
|
|
TGModernGalleryItemView *itemView = nil;
|
|
if (self.finishedTransitionIn && self.model.focusItem != nil)
|
|
{
|
|
for (TGModernGalleryItemView *visibleItemView in self->_visibleItemViews)
|
|
{
|
|
if ([visibleItemView.item isEqual:self.model.focusItem])
|
|
{
|
|
itemView = visibleItemView;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_finishedTransitionIn(_model.focusItem, itemView);
|
|
|
|
[_model _transitionCompleted];
|
|
}
|
|
else
|
|
[_model _transitionCompleted];
|
|
|
|
[_view transitionInWithDuration:0.15];
|
|
|
|
[self animateStatusBarTransition:0.2];
|
|
}
|
|
else
|
|
{
|
|
if (_finishedTransitionIn && _model.focusItem != nil)
|
|
{
|
|
TGModernGalleryItemView *itemView = nil;
|
|
if (self.finishedTransitionIn && self.model.focusItem != nil)
|
|
{
|
|
for (TGModernGalleryItemView *visibleItemView in self->_visibleItemViews)
|
|
{
|
|
if ([visibleItemView.item isEqual:self.model.focusItem])
|
|
{
|
|
itemView = visibleItemView;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_finishedTransitionIn(_model.focusItem, itemView);
|
|
}
|
|
|
|
[_model _transitionCompleted];
|
|
}
|
|
}
|
|
|
|
- (UIView *)findScrollView:(UIView *)view
|
|
{
|
|
if (view == nil || [view isKindOfClass:[UIScrollView class]])
|
|
return view;
|
|
|
|
return [self findScrollView:view.superview];
|
|
}
|
|
|
|
- (UIView *)topSuperviewOfView:(UIView *)view
|
|
{
|
|
if (view.superview == nil)
|
|
return view;
|
|
|
|
return [self topSuperviewOfView:view.superview];
|
|
}
|
|
|
|
- (UIView *)findCommonSuperviewOfView:(UIView *)view andView:(UIView *)andView
|
|
{
|
|
UIView *leftSuperview = [self topSuperviewOfView:view];
|
|
UIView *rightSuperview = [self topSuperviewOfView:andView];
|
|
|
|
if (leftSuperview != rightSuperview)
|
|
return nil;
|
|
|
|
return leftSuperview;
|
|
}
|
|
|
|
- (UIView *)subviewOfView:(UIView *)view containingView:(UIView *)containingView
|
|
{
|
|
if (view == containingView)
|
|
return view;
|
|
|
|
for (UIView *subview in view.subviews)
|
|
{
|
|
if ([self subviewOfView:subview containingView:containingView] != nil)
|
|
return subview;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
static CGRect adjustFrameForOriginalSubframe(CGRect originalFrame, CGRect originalSubframe, CGRect frame)
|
|
{
|
|
if (originalSubframe.size.width < FLT_EPSILON || originalSubframe.size.height < FLT_EPSILON)
|
|
return frame;
|
|
|
|
CGFloat widthFactor = frame.size.width / originalSubframe.size.width;
|
|
CGFloat heightFactor = frame.size.height / originalSubframe.size.height;
|
|
|
|
CGRect adjustedFrame = CGRectMake(frame.origin.x - originalSubframe.origin.x * widthFactor, frame.origin.y - originalSubframe.origin.y * heightFactor, originalFrame.size.width * widthFactor, originalFrame.size.height * heightFactor);
|
|
|
|
return adjustedFrame;
|
|
}
|
|
|
|
- (CGRect)convertFrameOfView:(UIView *)view fromSubframe:(CGRect)fromSubframe toView:(UIView *)toView toSubframe:(CGRect)toSubframe outRotationZ:(CGFloat *)outRotationZ
|
|
{
|
|
if (view == toView)
|
|
return view.bounds;
|
|
|
|
CGFloat sourceWindowRotation = 0.0f;
|
|
|
|
CGRect frame = fromSubframe;
|
|
|
|
UIView *currentView = view;
|
|
while (currentView != nil)
|
|
{
|
|
frame.origin.x += currentView.frame.origin.x - currentView.bounds.origin.x;
|
|
frame.origin.y += currentView.frame.origin.y - currentView.bounds.origin.y;
|
|
|
|
CGFloat rotation = transformRotation(currentView.transform);
|
|
if (ABS(rotation) > FLT_EPSILON)
|
|
{
|
|
CGAffineTransform transform = CGAffineTransformMakeTranslation(currentView.bounds.size.width / 2.0f, currentView.bounds.size.height / 2.0f);
|
|
transform = CGAffineTransformRotate(transform, rotation);
|
|
transform = CGAffineTransformTranslate(transform, -currentView.bounds.size.width / 2.0f, -currentView.bounds.size.height / 2.0f);
|
|
|
|
frame = CGRectApplyAffineTransform(frame, transform);
|
|
}
|
|
|
|
//TGLog(@"%f: %@", rotation, currentView);
|
|
|
|
if ([currentView.superview isKindOfClass:[UIWindow class]])
|
|
sourceWindowRotation = rotation;
|
|
|
|
//frame = CGRectApplyAffineTransform(frame, transform);
|
|
|
|
currentView = currentView.superview;
|
|
}
|
|
|
|
UIView *subview = [self topSuperviewOfView:toView];
|
|
while (subview != nil && subview != toView)
|
|
{
|
|
frame.origin.x -= subview.frame.origin.x - subview.bounds.origin.x;
|
|
frame.origin.y -= subview.frame.origin.y - subview.bounds.origin.y;
|
|
|
|
if (ABS(sourceWindowRotation) > FLT_EPSILON)
|
|
{
|
|
CGAffineTransform transform = CGAffineTransformMakeTranslation(subview.bounds.size.width / 2.0f, subview.bounds.size.height / 2.0f);
|
|
transform = CGAffineTransformRotate(transform, -sourceWindowRotation);
|
|
transform = CGAffineTransformTranslate(transform, -subview.bounds.size.width / 2.0f, -subview.bounds.size.height / 2.0f);
|
|
|
|
frame = CGRectApplyAffineTransform(frame, transform);
|
|
|
|
sourceWindowRotation = 0.0f;
|
|
}
|
|
|
|
subview = [self subviewOfView:subview containingView:toView];
|
|
}
|
|
|
|
frame = adjustFrameForOriginalSubframe(toView.frame, toSubframe, frame);
|
|
|
|
/*frame.origin.x = CGFloor(frame.origin.x);
|
|
frame.origin.y = CGFloor(frame.origin.y);
|
|
frame.size.width = CGFloor(frame.size.width);
|
|
frame.size.height = CGFloor(frame.size.height);*/
|
|
|
|
if (outRotationZ != NULL)
|
|
*outRotationZ = 0.0f;
|
|
|
|
return frame;
|
|
}
|
|
|
|
static CGFloat transformRotation(CGAffineTransform transform)
|
|
{
|
|
return (CGFloat)atan2(transform.b, transform.a);
|
|
}
|
|
|
|
- (void)animateView:(UIView *)view frameFrom:(CGRect)fromFrame to:(CGRect)toFrame velocity:(CGPoint)__unused velocity rotationFrom:(CGFloat)fromRotation to:(CGFloat)toRotation animatingIn:(bool)animatingIn completion:(void (^)(bool))completion
|
|
{
|
|
if (fromFrame.size.width < FLT_EPSILON || fromFrame.size.height < FLT_EPSILON )
|
|
{
|
|
completion(true);
|
|
return;
|
|
}
|
|
|
|
if (ABS(toRotation - fromRotation) > FLT_EPSILON)
|
|
{
|
|
}
|
|
|
|
[CATransaction begin];
|
|
|
|
CGFloat damping = animatingIn ? 25.0f : 30.0f;
|
|
CGFloat mass = 0.8f;
|
|
CGFloat durationFactor = animatingIn ? 1.8f : 2.0f;
|
|
|
|
CGPoint fromPosition = CGPointMake(CGRectGetMidX(fromFrame), CGRectGetMidY(fromFrame));
|
|
CGPoint toPosition = CGPointMake(CGRectGetMidX(toFrame), CGRectGetMidY(toFrame));
|
|
JNWSpringAnimation *positionAnimation = [JNWSpringAnimation animationWithKeyPath:@"position"];
|
|
positionAnimation.fromValue = [NSValue valueWithCGPoint:fromPosition];
|
|
positionAnimation.toValue = [NSValue valueWithCGPoint:toPosition];
|
|
positionAnimation.damping = damping;
|
|
positionAnimation.mass = mass;
|
|
positionAnimation.removedOnCompletion = true;
|
|
positionAnimation.fillMode = kCAFillModeForwards;
|
|
positionAnimation.durationFactor = durationFactor;
|
|
TGAnimationBlockDelegate *delegate = [[TGAnimationBlockDelegate alloc] initWithLayer:view.layer];
|
|
delegate.completion = ^(BOOL finished)
|
|
{
|
|
if (completion)
|
|
completion(finished);
|
|
};
|
|
positionAnimation.delegate = delegate;
|
|
view.layer.position = toPosition;
|
|
[view.layer addAnimation:positionAnimation forKey:@"position"];
|
|
|
|
CGPoint fromScale = CGPointMake(fromFrame.size.width / view.bounds.size.width, fromFrame.size.height/ view.bounds.size.height);
|
|
CGPoint toScale = CGPointMake(toFrame.size.width / view.bounds.size.width, toFrame.size.height / view.bounds.size.height);
|
|
view.layer.transform = CATransform3DMakeScale(toFrame.size.width / view.bounds.size.width, toFrame.size.height / view.bounds.size.height, 1.0f);
|
|
{
|
|
JNWSpringAnimation *scaleAnimation = [JNWSpringAnimation animationWithKeyPath:@"transform.scale.x"];
|
|
scaleAnimation.fromValue = @(fromScale.x);
|
|
scaleAnimation.toValue = @(toScale.x);
|
|
scaleAnimation.damping = damping;
|
|
scaleAnimation.mass = mass;
|
|
scaleAnimation.removedOnCompletion = true;
|
|
scaleAnimation.fillMode = kCAFillModeForwards;
|
|
scaleAnimation.durationFactor = durationFactor;
|
|
[view.layer addAnimation:scaleAnimation forKey:@"transform.scale.x"];
|
|
}
|
|
{
|
|
JNWSpringAnimation *scaleAnimation = [JNWSpringAnimation animationWithKeyPath:@"transform.scale.y"];
|
|
scaleAnimation.fromValue = @(fromScale.y);
|
|
scaleAnimation.toValue = @(toScale.y);
|
|
scaleAnimation.damping = damping;
|
|
scaleAnimation.mass = mass;
|
|
scaleAnimation.removedOnCompletion = true;
|
|
scaleAnimation.fillMode = kCAFillModeForwards;
|
|
scaleAnimation.durationFactor = durationFactor;
|
|
[view.layer addAnimation:scaleAnimation forKey:@"transform.scale.y"];
|
|
}
|
|
|
|
[CATransaction commit];
|
|
}
|
|
|
|
- (void)animateTransitionInFromView:(UIView *)fromView toView:(UIView *)toView toViewContentRect:(CGRect)toViewContentRect
|
|
{
|
|
UIView *fromScrollView = [self findScrollView:fromView];
|
|
UIView *fromContainerView = fromScrollView.superview;
|
|
|
|
CGFloat fromRotationZ = 0.0f;
|
|
CGRect fromFrame = [self convertFrameOfView:fromView fromSubframe:(CGRect){CGPointZero, fromView.frame.size} toView:toView.superview toSubframe:[toView.superview convertRect:toViewContentRect fromView:toView] outRotationZ:&fromRotationZ];
|
|
|
|
if (![fromScrollView isKindOfClass:[TGSharedMediaCollectionView class]])
|
|
fromFrame.origin.y += fromScrollView.frame.origin.y * 2.0f;
|
|
|
|
fromFrame.origin.x -= toView.superview.frame.origin.x;
|
|
fromFrame.origin.y -= toView.superview.frame.origin.y;
|
|
|
|
CGRect fromContainerFromFrame = [fromContainerView convertRect:fromView.bounds fromView:fromView];
|
|
CGRect fromContainerFrame = [self convertFrameOfView:toView fromSubframe:toViewContentRect toView:fromContainerView toSubframe:(CGRect){CGPointZero, fromContainerView.frame.size} outRotationZ:NULL];
|
|
|
|
if ([fromView conformsToProtocol:@protocol(TGModernGalleryTransitionView)] && [fromView respondsToSelector:@selector(transitionContentRect)])
|
|
{
|
|
CGRect fromContentRect = [(id<TGModernGalleryTransitionView>)fromView transitionContentRect];
|
|
if (fromContentRect.size.width > FLT_EPSILON && fromContentRect.size.height > FLT_EPSILON)
|
|
{
|
|
fromContainerFrame = adjustFrameForOriginalSubframe(fromView.frame, fromContentRect, fromContainerFrame);
|
|
}
|
|
}
|
|
|
|
UIView *fromViewContainerCopy = nil;
|
|
|
|
if ([fromView conformsToProtocol:@protocol(TGModernGalleryTransitionView)])
|
|
{
|
|
UIImage *transitionImage = [(id<TGModernGalleryTransitionView>)fromView transitionImage];
|
|
if (transitionImage != nil)
|
|
fromViewContainerCopy = [[UIImageView alloc] initWithImage:transitionImage];
|
|
}
|
|
|
|
if (fromViewContainerCopy == nil)
|
|
fromViewContainerCopy = [fromView snapshotViewAfterScreenUpdates:false];
|
|
|
|
fromViewContainerCopy.frame = fromContainerFromFrame;
|
|
[fromContainerView insertSubview:fromViewContainerCopy aboveSubview:fromScrollView];
|
|
|
|
__weak TGModernGalleryController *weakSelf = self;
|
|
self.view.userInteractionEnabled = false;
|
|
[self animateView:toView frameFrom:fromFrame to:toView.frame velocity:CGPointZero rotationFrom:fromRotationZ to:0.0f animatingIn:true completion:^(bool __unused finished)
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
strongSelf.view.userInteractionEnabled = true;
|
|
|
|
TGModernGalleryItemView *itemView = nil;
|
|
if (strongSelf.finishedTransitionIn && strongSelf.model.focusItem != nil)
|
|
{
|
|
for (TGModernGalleryItemView *visibleItemView in strongSelf->_visibleItemViews)
|
|
{
|
|
if ([visibleItemView.item isEqual:strongSelf.model.focusItem])
|
|
{
|
|
itemView = visibleItemView;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strongSelf.finishedTransitionIn)
|
|
strongSelf.finishedTransitionIn(strongSelf.model.focusItem, itemView);
|
|
|
|
strongSelf->_preloadVisibleItemViews = true;
|
|
[strongSelf scrollViewBoundsChanged:strongSelf->_view.scrollView.bounds synchronously:false];
|
|
|
|
[strongSelf.model _transitionCompleted];
|
|
}
|
|
}];
|
|
|
|
__weak UIView *weakFromViewContainerCopy = fromViewContainerCopy;
|
|
[self animateView:fromViewContainerCopy frameFrom:fromViewContainerCopy.frame to:fromContainerFrame velocity:CGPointZero rotationFrom:0.0f to:0.0f animatingIn:true completion:^(__unused bool finished)
|
|
{
|
|
__strong UIView *strongFromViewContainerCopy = weakFromViewContainerCopy;
|
|
[strongFromViewContainerCopy removeFromSuperview];
|
|
}];
|
|
|
|
toView.alpha = 0.0f;
|
|
|
|
/*POPBasicAnimation *toViewAlphaAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
|
|
toViewAlphaAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
|
toViewAlphaAnimation.duration = 0.15;
|
|
toViewAlphaAnimation.fromValue = @(0.0);
|
|
toViewAlphaAnimation.toValue = @(1.0);
|
|
[toView pop_addAnimation:toViewAlphaAnimation forKey:@"transitionInAlpha"];*/
|
|
[UIView animateWithDuration:0.07 animations:^{
|
|
toView.alpha = 1.0f;
|
|
}];
|
|
}
|
|
|
|
- (void)animateTransitionOutFromView:(UIView *)fromView fromViewContentRect:(CGRect)fromViewContentRect toView:(UIView *)toView velocity:(CGPoint)velocity
|
|
{
|
|
TGLog(@"begin calc");
|
|
|
|
TG_TIMESTAMP_DEFINE(out)
|
|
|
|
UIView *toScrollView = [self findScrollView:toView];
|
|
UIView *toContainerView = toScrollView.superview;
|
|
|
|
CGRect toFrame = [fromView.superview convertRect:[toView convertRect:toView.bounds toView:nil] fromView:nil];
|
|
toFrame = adjustFrameForOriginalSubframe(fromView.frame, fromViewContentRect, toFrame);
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
CGRect toContainerFrame = [toContainerView convertRect:toView.bounds fromView:toView];
|
|
CGRect toContainerFromFrame = [toContainerView convertRect:[fromView convertRect:fromViewContentRect toView:nil] fromView:nil];
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
/*POPBasicAnimation *fromViewAlphaAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
|
|
fromViewAlphaAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
|
fromViewAlphaAnimation.duration = 0.15;
|
|
fromViewAlphaAnimation.fromValue = @(1.0);
|
|
fromViewAlphaAnimation.toValue = @(0.0);
|
|
[fromView pop_addAnimation:fromViewAlphaAnimation forKey:@"transitionOutAlpha"];*/
|
|
[UIView animateWithDuration:0.1 animations:^{
|
|
fromView.alpha = 0.0f;
|
|
}];
|
|
|
|
UIView *toViewCopy = nil;
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
if ([toView conformsToProtocol:@protocol(TGModernGalleryTransitionView)])
|
|
{
|
|
UIImage *transitionImage = [(id<TGModernGalleryTransitionView>)toView transitionImage];
|
|
if (transitionImage != nil)
|
|
{
|
|
toViewCopy = [[UIImageView alloc] initWithImage:transitionImage];
|
|
}
|
|
}
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
if (toViewCopy == nil)
|
|
{
|
|
CGFloat toViewAlpha = toView.alpha;
|
|
bool toViewHidden = toView.hidden;
|
|
CGRect toViewFrame = toView.frame;
|
|
toView.alpha = 1.0f;
|
|
toView.hidden = false;
|
|
toView.frame = CGRectOffset(toViewFrame, 1000.0f, 0.0f);
|
|
toViewCopy = [toView snapshotViewAfterScreenUpdates:true];
|
|
toView.alpha = toViewAlpha;
|
|
toView.hidden = toViewHidden;
|
|
toView.frame = toViewFrame;
|
|
}
|
|
|
|
toViewCopy.frame = toContainerFromFrame;
|
|
[toContainerView insertSubview:toViewCopy aboveSubview:toScrollView];
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
__weak TGModernGalleryController *weakSelf = self;
|
|
self.view.userInteractionEnabled = false;
|
|
[self animateView:fromView frameFrom:fromView.frame to:toFrame velocity:velocity rotationFrom:0.0f to:0.0f animatingIn:false completion:^(__unused bool finished)
|
|
{
|
|
__strong TGModernGalleryController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
[strongSelf dismiss];
|
|
}
|
|
}];
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
__weak UIView *weakToViewCopy = toViewCopy;
|
|
[self animateView:toViewCopy frameFrom:toViewCopy.frame to:toContainerFrame velocity:velocity rotationFrom:0.0f to:0.0f animatingIn:false completion:^(__unused bool finished)
|
|
{
|
|
__strong UIView *strongToViewCopy = weakToViewCopy;
|
|
[strongToViewCopy removeFromSuperview];
|
|
}];
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
if (iosMajorVersion() >= 7)
|
|
{
|
|
[self animateStatusBarTransition:0.2];
|
|
self->_statusBarStyle = UIStatusBarStyleDefault;
|
|
[self setNeedsStatusBarAppearanceUpdate];
|
|
}
|
|
|
|
TG_TIMESTAMP_MEASURE(out)
|
|
|
|
if (self.adjustsStatusBarVisibility)
|
|
{
|
|
[UIView animateWithDuration:0.2 animations:^
|
|
{
|
|
[TGHacks setApplicationStatusBarAlpha:1.0f];
|
|
}];
|
|
}
|
|
|
|
TGLog(@"started animation");
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
if (!_showInterface)
|
|
{
|
|
_view.interfaceView.alpha = 0.0f;
|
|
|
|
if (self.adjustsStatusBarVisibility)
|
|
[TGHacks setApplicationStatusBarAlpha:0.0f];
|
|
}
|
|
else if ([_view.interfaceView prefersStatusBarHidden])
|
|
{
|
|
if (self.adjustsStatusBarVisibility)
|
|
[TGHacks setApplicationStatusBarAlpha:0.0f];
|
|
}
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated
|
|
{
|
|
[super viewWillDisappear:animated];
|
|
|
|
if (self.adjustsStatusBarVisibility)
|
|
{
|
|
[TGHacks setApplicationStatusBarAlpha:1.0f];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^
|
|
{
|
|
[TGHacks setApplicationStatusBarAlpha:1.0f];
|
|
});
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)setCurrentItemIndex:(NSUInteger)index animated:(bool)animated
|
|
{
|
|
[self setCurrentItemIndex:index direction:TGModernGalleryScrollAnimationDirectionDefault animated:animated];
|
|
}
|
|
|
|
- (void)setCurrentItemIndex:(NSUInteger)index direction:(TGModernGalleryScrollAnimationDirection)direction animated:(bool)animated
|
|
{
|
|
if (index == [self currentItemIndex])
|
|
return;
|
|
|
|
if (animated)
|
|
{
|
|
UIView *currentItemView = [self itemViewForItem:[self currentItem]];
|
|
|
|
UIView *outgoingItemView = [currentItemView snapshotViewAfterScreenUpdates:false];
|
|
[_view insertSubview:outgoingItemView belowSubview:_view.interfaceView];
|
|
|
|
if (direction == TGModernGalleryScrollAnimationDirectionDefault)
|
|
direction = (index > [self currentItemIndex]) ? TGModernGalleryScrollAnimationDirectionRight : TGModernGalleryScrollAnimationDirectionLeft;
|
|
|
|
[self setCurrentItemIndex:index synchronously:false];
|
|
|
|
UIView *incomingItemView = [self itemViewForItem:[self currentItem]];
|
|
CGPoint incomingItemStartPosition = incomingItemView.center;
|
|
CGPoint incomingItemTargetPosition = incomingItemView.center;
|
|
|
|
CGPoint outgoingItemTargetPosition = CGPointMake(0, incomingItemTargetPosition.y);
|
|
|
|
CGSize referenceSize = TGAppDelegateInstance.rootController.applicationBounds.size;
|
|
|
|
switch (direction)
|
|
{
|
|
case TGModernGalleryScrollAnimationDirectionLeft:
|
|
incomingItemStartPosition.x += -referenceSize.width - 2 * TGModernGalleryItemPadding;
|
|
outgoingItemTargetPosition.x = referenceSize.width * 1.5f + 2 * TGModernGalleryItemPadding;
|
|
break;
|
|
|
|
case TGModernGalleryScrollAnimationDirectionRight:
|
|
incomingItemStartPosition.x += referenceSize.width + 2 * TGModernGalleryItemPadding;
|
|
outgoingItemTargetPosition.x = -(referenceSize.width / 2) - 2 * TGModernGalleryItemPadding;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
incomingItemView.center = incomingItemStartPosition;
|
|
|
|
[UIView animateWithDuration:0.3f
|
|
delay:0.0f
|
|
options:UIViewAnimationOptionCurveEaseInOut
|
|
animations:^
|
|
{
|
|
incomingItemView.center = incomingItemTargetPosition;
|
|
outgoingItemView.center = outgoingItemTargetPosition;
|
|
} completion:^(__unused BOOL finished)
|
|
{
|
|
[outgoingItemView removeFromSuperview];
|
|
[self _updateItemViewsCurrent];
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
[self setCurrentItemIndex:index synchronously:false];
|
|
}
|
|
}
|
|
|
|
- (id<TGModernGalleryItem>)currentItem
|
|
{
|
|
return _model.items.count > 0 ? [_model.items objectAtIndex:[self currentItemIndex]] : nil;
|
|
}
|
|
|
|
- (void)setCurrentItemIndex:(NSUInteger)currentItemIndex synchronously:(bool)synchronously
|
|
{
|
|
_synchronousBoundsChange = synchronously;
|
|
_view.scrollView.bounds = CGRectMake(_view.scrollView.bounds.size.width * currentItemIndex, 0.0f, _view.scrollView.bounds.size.width, _view.scrollView.bounds.size.height);
|
|
_synchronousBoundsChange = false;
|
|
}
|
|
|
|
- (NSUInteger)currentItemIndex
|
|
{
|
|
return _model.items.count == 0 ? 0 : (NSUInteger)[self currentItemFuzzyIndex];
|
|
}
|
|
|
|
- (CGFloat)currentItemFuzzyIndex
|
|
{
|
|
if (_model.items.count == 0)
|
|
return 0.0f;
|
|
|
|
return CGFloor((_view.scrollView.bounds.origin.x + _view.scrollView.bounds.size.width / 2.0f) / _view.scrollView.bounds.size.width);
|
|
}
|
|
|
|
- (NSArray *)visibleItems
|
|
{
|
|
NSMutableArray *items = [[NSMutableArray alloc] init];
|
|
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
if (itemView.item != nil)
|
|
[items addObject:itemView.item];
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
- (void)reloadDataAtItem:(id<TGModernGalleryItem>)atItem synchronously:(bool)synchronously
|
|
{
|
|
NSMutableIndexSet *removeIndices = nil;
|
|
|
|
id<TGModernGalleryItem> focusItem = atItem;
|
|
|
|
if (focusItem == nil)
|
|
{
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
if (itemView.index == [self currentItemIndex])
|
|
{
|
|
focusItem = itemView.item;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
NSInteger itemViewIndex = -1;
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
itemViewIndex++;
|
|
|
|
NSInteger itemIndex = -1;
|
|
bool itemFound = false;
|
|
for (id<TGModernGalleryItem> item in _model.items)
|
|
{
|
|
itemIndex++;
|
|
|
|
if ([item isEqual:itemView.item])
|
|
{
|
|
itemView.index = (NSUInteger)itemIndex;
|
|
itemFound = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!itemFound)
|
|
{
|
|
if (removeIndices == nil)
|
|
removeIndices = [[NSMutableIndexSet alloc] init];
|
|
[removeIndices addIndex:(NSUInteger)itemViewIndex];
|
|
|
|
UIView *itemHeaderView = [itemView headerView];
|
|
if (itemHeaderView != nil)
|
|
[_view removeItemHeaderView:itemHeaderView];
|
|
|
|
UIView *itemDefaultLeftAcessoryView = [itemView defaultFooterAccessoryLeftView];
|
|
if (itemDefaultLeftAcessoryView != nil)
|
|
[_view.interfaceView removeItemLeftAcessoryView:itemDefaultLeftAcessoryView];
|
|
|
|
UIView *itemDefaultRightAcessoryView = [itemView defaultFooterAccessoryRightView];
|
|
if (itemDefaultRightAcessoryView != nil)
|
|
[_view.interfaceView removeItemRightAcessoryView:itemDefaultRightAcessoryView];
|
|
|
|
UIView *itemFooterView = [itemView footerView];
|
|
if (itemFooterView != nil)
|
|
[_view removeItemFooterView:itemFooterView];
|
|
[itemView removeFromSuperview];
|
|
[self enqueueView:itemView];
|
|
}
|
|
}
|
|
|
|
if (removeIndices != nil)
|
|
[_visibleItemViews removeObjectsAtIndexes:removeIndices];
|
|
|
|
_reloadingItems = true;
|
|
|
|
NSUInteger index = (focusItem == nil || _model.items.count == 0) ? NSNotFound : [_model.items indexOfObject:focusItem];
|
|
if (index != NSNotFound && index != _lastReportedFocusedIndex)
|
|
{
|
|
_lastReportedFocusedIndex = NSNotFound;
|
|
[self setCurrentItemIndex:index == NSNotFound ? 0 : index synchronously:synchronously];
|
|
}
|
|
else
|
|
{
|
|
_lastReportedFocusedIndex = NSNotFound;
|
|
|
|
CGFloat itemWidth = _view.scrollView.bounds.size.width;
|
|
CGSize contentSize = CGSizeMake(_model.items.count * itemWidth, _view.scrollView.bounds.size.height);
|
|
if (!CGSizeEqualToSize(_view.scrollView.contentSize, contentSize))
|
|
{
|
|
_view.scrollView.contentSize = contentSize;
|
|
if (_view.scrollView.bounds.origin.x > contentSize.width - itemWidth)
|
|
{
|
|
_view.scrollView.bounds = CGRectMake(contentSize.width - itemWidth, 0.0f, itemWidth, _view.scrollView.bounds.size.height);
|
|
}
|
|
else
|
|
[self scrollViewBoundsChanged:_view.scrollView.bounds];
|
|
}
|
|
else
|
|
[self scrollViewBoundsChanged:_view.scrollView.bounds];
|
|
}
|
|
|
|
_reloadingItems = false;
|
|
}
|
|
|
|
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)__unused scrollView
|
|
{
|
|
}
|
|
|
|
- (void)scrollViewBoundsChanged:(CGRect)bounds
|
|
{
|
|
[self scrollViewBoundsChanged:bounds synchronously:_synchronousBoundsChange];
|
|
}
|
|
|
|
- (void)scrollViewBoundsChanged:(CGRect)bounds synchronously:(bool)synchronously
|
|
{
|
|
if (_view == nil)
|
|
return;
|
|
|
|
CGFloat itemWidth = bounds.size.width;
|
|
|
|
NSUInteger leftmostVisibleItemIndex = 0;
|
|
if (bounds.origin.x > 0.0f)
|
|
leftmostVisibleItemIndex = (NSUInteger)floor((bounds.origin.x + 1.0f) / itemWidth);
|
|
NSUInteger leftmostTrulyVisibleItemIndex = leftmostVisibleItemIndex;
|
|
|
|
NSUInteger rightmostVisibleItemIndex = _model.items.count - 1;
|
|
if (bounds.origin.x + bounds.size.width < _model.items.count * itemWidth)
|
|
rightmostVisibleItemIndex = (NSUInteger)CGFloor((bounds.origin.x + bounds.size.width - 1.0f) / itemWidth);
|
|
NSUInteger rightmostTrulyVisibleItemIndex = rightmostVisibleItemIndex;
|
|
|
|
if (_preloadVisibleItemViews)
|
|
{
|
|
if (leftmostVisibleItemIndex >= 1)
|
|
leftmostVisibleItemIndex = leftmostVisibleItemIndex - 1;
|
|
if (rightmostVisibleItemIndex < _model.items.count - 1)
|
|
rightmostVisibleItemIndex = rightmostVisibleItemIndex + 1;
|
|
}
|
|
|
|
if (leftmostVisibleItemIndex <= rightmostVisibleItemIndex && _model.items.count != 0)
|
|
{
|
|
CGSize contentSize = CGSizeMake(_model.items.count * itemWidth, bounds.size.height);
|
|
if (!CGSizeEqualToSize(_view.scrollView.contentSize, contentSize))
|
|
_view.scrollView.contentSize = contentSize;
|
|
|
|
NSUInteger loadedVisibleViewIndices[16];
|
|
NSUInteger loadedVisibleViewIndexCount = 0;
|
|
|
|
NSUInteger visibleViewCount = _visibleItemViews.count;
|
|
for (NSUInteger i = 0; i < visibleViewCount; i++)
|
|
{
|
|
TGModernGalleryItemView *itemView = _visibleItemViews[i];
|
|
if (itemView.index < leftmostVisibleItemIndex || itemView.index > rightmostVisibleItemIndex)
|
|
{
|
|
UIView *itemHeaderView = [itemView headerView];
|
|
if (itemHeaderView != nil)
|
|
[_view removeItemHeaderView:itemHeaderView];
|
|
|
|
UIView *itemDefaultLeftAcessoryView = [itemView defaultFooterAccessoryLeftView];
|
|
if (itemDefaultLeftAcessoryView != nil)
|
|
[_view.interfaceView removeItemLeftAcessoryView:itemDefaultLeftAcessoryView];
|
|
|
|
UIView *itemDefaultRightAcessoryView = [itemView defaultFooterAccessoryRightView];
|
|
if (itemDefaultRightAcessoryView != nil)
|
|
[_view.interfaceView removeItemRightAcessoryView:itemDefaultRightAcessoryView];
|
|
|
|
UIView *itemFooterView = [itemView footerView];
|
|
if (itemFooterView != nil)
|
|
[_view removeItemFooterView:itemFooterView];
|
|
|
|
[self enqueueView:itemView];
|
|
[itemView removeFromSuperview];
|
|
[_visibleItemViews removeObjectAtIndex:i];
|
|
i--;
|
|
visibleViewCount--;
|
|
}
|
|
else
|
|
{
|
|
if (loadedVisibleViewIndexCount < 16)
|
|
loadedVisibleViewIndices[loadedVisibleViewIndexCount++] = itemView.index;
|
|
|
|
[itemView setIsVisible:itemView.index >= leftmostTrulyVisibleItemIndex && itemView.index <= rightmostTrulyVisibleItemIndex];
|
|
[itemView setIsCurrent:itemView.index == [self currentItemIndex]];
|
|
|
|
CGRect itemFrame = CGRectMake(itemWidth * itemView.index + TGModernGalleryItemPadding, 0.0f, itemWidth - TGModernGalleryItemPadding * 2.0f, bounds.size.height);
|
|
if (!CGRectEqualToRect(itemView.frame, itemFrame))
|
|
itemView.frame = itemFrame;
|
|
}
|
|
}
|
|
|
|
for (NSUInteger i = leftmostVisibleItemIndex; i <= rightmostVisibleItemIndex; i++)
|
|
{
|
|
bool itemHasVisibleView = false;
|
|
for (NSUInteger j = 0; j < loadedVisibleViewIndexCount; j++)
|
|
{
|
|
if (loadedVisibleViewIndices[j] == i)
|
|
{
|
|
itemHasVisibleView = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!itemHasVisibleView)
|
|
{
|
|
id<TGModernGalleryItem> item = _model.items[i];
|
|
TGModernGalleryItemView *itemView = [self dequeueViewForItem:item];
|
|
if (itemView != nil)
|
|
{
|
|
itemView.frame = CGRectMake(itemWidth * i + TGModernGalleryItemPadding, 0.0f, itemWidth - TGModernGalleryItemPadding * 2.0f, bounds.size.height);
|
|
[itemView setItem:item synchronously:synchronously];
|
|
itemView.index = i;
|
|
[_view.scrollView addSubview:itemView];
|
|
|
|
UIView *headerView = [itemView headerView];
|
|
if (headerView != nil)
|
|
[_view addItemHeaderView:headerView];
|
|
|
|
UIView *itemDefaultLeftAcessoryView = [itemView defaultFooterAccessoryLeftView];
|
|
if (itemDefaultLeftAcessoryView != nil)
|
|
[_view.interfaceView addItemLeftAcessoryView:itemDefaultLeftAcessoryView];
|
|
|
|
UIView *itemDefaultRightAcessoryView = [itemView defaultFooterAccessoryRightView];
|
|
if (itemDefaultRightAcessoryView != nil)
|
|
[_view.interfaceView addItemRightAcessoryView:itemDefaultRightAcessoryView];
|
|
|
|
UIView *footerView = [itemView footerView];
|
|
if (footerView != nil)
|
|
[_view addItemFooterView:footerView];
|
|
[_visibleItemViews addObject:itemView];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (_visibleItemViews.count != 0)
|
|
{
|
|
_view.scrollView.contentSize = CGSizeZero;
|
|
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
UIView *itemHeaderView = [itemView headerView];
|
|
if (itemHeaderView != nil)
|
|
[_view removeItemHeaderView:itemHeaderView];
|
|
|
|
UIView *itemDefaultLeftAcessoryView = [itemView defaultFooterAccessoryLeftView];
|
|
if (itemDefaultLeftAcessoryView != nil)
|
|
[_view.interfaceView addItemLeftAcessoryView:itemDefaultLeftAcessoryView];
|
|
|
|
UIView *itemDefaultRightAcessoryView = [itemView defaultFooterAccessoryRightView];
|
|
if (itemDefaultRightAcessoryView != nil)
|
|
[_view.interfaceView addItemRightAcessoryView:itemDefaultRightAcessoryView];
|
|
|
|
UIView *itemFooterView = [itemView footerView];
|
|
if (itemFooterView != nil)
|
|
[_view removeItemFooterView:itemFooterView];
|
|
|
|
[itemView removeFromSuperview];
|
|
[self enqueueView:itemView];
|
|
}
|
|
[_visibleItemViews removeAllObjects];
|
|
}
|
|
|
|
CGFloat fuzzyIndex = MAX(0, MIN(_model.items.count - 1, (_view.scrollView.bounds.origin.x) / _view.scrollView.bounds.size.width));
|
|
CGFloat titleAlpha = 1.0f;
|
|
|
|
NSUInteger currentItemIndex = [self currentItemIndex];
|
|
TGModernGalleryItemView *currentItemView = nil;
|
|
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
CGFloat alpha = MAX(0.0f, MIN(1.0f, 1.0f - ABS(itemView.index - fuzzyIndex)));
|
|
|
|
UIView *itemHeaderView = [itemView headerView];
|
|
if (itemHeaderView != nil)
|
|
{
|
|
itemHeaderView.alpha = alpha;
|
|
titleAlpha -= alpha;
|
|
}
|
|
|
|
CGFloat footerAlpha = itemView.index == currentItemIndex ? 1.0f : 0.0f;
|
|
|
|
if (itemView.index == currentItemIndex)
|
|
currentItemView = itemView;
|
|
|
|
UIView *itemDefaultLeftAcessoryView = [itemView defaultFooterAccessoryLeftView];
|
|
if (itemDefaultLeftAcessoryView != nil)
|
|
itemDefaultLeftAcessoryView.alpha = footerAlpha;
|
|
|
|
UIView *itemDefaultRightAcessoryView = [itemView defaultFooterAccessoryRightView];
|
|
if (itemDefaultRightAcessoryView != nil)
|
|
itemDefaultRightAcessoryView.alpha = footerAlpha;
|
|
|
|
UIView *itemFooterView = [itemView footerView];
|
|
if (itemFooterView != nil)
|
|
itemFooterView.alpha = footerAlpha;
|
|
}
|
|
|
|
_defaultHeaderView.alpha = MAX(0.0f, MIN(1.0f, titleAlpha));
|
|
|
|
if (_lastReportedFocusedIndex != [self currentItemIndex])
|
|
{
|
|
if (!_reloadingItems && _lastReportedFocusedIndex != NSNotFound)
|
|
[_view updateInterfaceVisibility];
|
|
|
|
NSUInteger previousFocusedIndex = _lastReportedFocusedIndex;
|
|
_lastReportedFocusedIndex = [self currentItemIndex];
|
|
|
|
if (_lastReportedFocusedIndex < _model.items.count)
|
|
{
|
|
if (_itemFocused)
|
|
_itemFocused(_model.items[_lastReportedFocusedIndex]);
|
|
|
|
[_view.interfaceView itemFocused:_model.items[_lastReportedFocusedIndex] itemView:currentItemView];
|
|
|
|
[_defaultHeaderView setItem:_model.items[_lastReportedFocusedIndex]];
|
|
[_defaultFooterView setItem:_model.items[_lastReportedFocusedIndex]];
|
|
|
|
if (previousFocusedIndex < _model.items.count)
|
|
[[self itemViewForItem:_model.items[previousFocusedIndex]] setFocused:false];
|
|
[[self itemViewForItem:_model.items[_lastReportedFocusedIndex]] setFocused:true];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)_updateItemViewsCurrent
|
|
{
|
|
NSUInteger visibleViewCount = _visibleItemViews.count;
|
|
for (NSUInteger i = 0; i < visibleViewCount; i++)
|
|
{
|
|
TGModernGalleryItemView *itemView = _visibleItemViews[i];
|
|
//[itemView setIsVisible:itemView.index >= leftmostTrulyVisibleItemIndex && itemView.index <= rightmostTrulyVisibleItemIndex];
|
|
[itemView setIsCurrent:itemView.index == [self currentItemIndex]];
|
|
}
|
|
}
|
|
|
|
- (bool)scrollViewShouldScrollWithTouchAtPoint:(CGPoint)point
|
|
{
|
|
NSUInteger currentItemIndex = [self currentItemIndex];
|
|
|
|
for (TGModernGalleryItemView *itemView in _visibleItemViews)
|
|
{
|
|
if (itemView.index == currentItemIndex)
|
|
{
|
|
CGPoint localPoint = [itemView convertPoint:point fromView:_view.scrollView];
|
|
return [itemView allowsScrollingAtPoint:localPoint];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
- (void)animateStatusBarTransition:(NSTimeInterval)duration
|
|
{
|
|
if (iosMajorVersion() >= 7)
|
|
{
|
|
[TGHacks animateApplicationStatusBarStyleTransitionWithDuration:duration];
|
|
}
|
|
}
|
|
|
|
- (void)processKeyCommand:(UIKeyCommand *)keyCommand
|
|
{
|
|
if ([keyCommand.input isEqualToString:UIKeyInputLeftArrow])
|
|
{
|
|
NSInteger newIndex = [self currentItemIndex] - 1;
|
|
if (newIndex >= 0)
|
|
[self setCurrentItemIndex:newIndex animated:true];
|
|
}
|
|
else if ([keyCommand.input isEqualToString:UIKeyInputRightArrow])
|
|
{
|
|
NSUInteger newIndex = [self currentItemIndex] + 1;
|
|
if (newIndex < _model.items.count)
|
|
[self setCurrentItemIndex:newIndex animated:true];
|
|
}
|
|
else if ([keyCommand.input isEqualToString:UIKeyInputEscape])
|
|
{
|
|
_view.transitionOut(0.0f);
|
|
}
|
|
}
|
|
|
|
- (NSArray *)availableKeyCommands
|
|
{
|
|
return @
|
|
[
|
|
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputLeftArrow modifierFlags:0],
|
|
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputRightArrow modifierFlags:0],
|
|
[TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0],
|
|
[TGKeyCommand keyCommandWithTitle:nil input:@"\t" modifierFlags:0]
|
|
];
|
|
}
|
|
|
|
- (bool)isExclusive
|
|
{
|
|
return true;
|
|
}
|
|
|
|
@end
|