1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-11 08:59:48 +01:00
Telegram/Telegraph/TGAttachmentCarouselItemView.m

1053 lines
39 KiB
Mathematica
Raw Normal View History

2016-02-25 01:03:51 +01:00
#import "TGAttachmentCarouselItemView.h"
#import "TGMenuSheetButtonItemView.h"
#import "TGMenuSheetView.h"
#import "UICollectionView+Utils.h"
#import "TGImageUtils.h"
#import "TGStringUtils.h"
#import "TGPhotoEditorUtils.h"
#import "TGMediaEditingContext.h"
#import "TGMediaSelectionContext.h"
#import "TGTransitionLayout.h"
#import "TGAttachmentCameraView.h"
#import "TGAttachmentPhotoCell.h"
#import "TGAttachmentVideoCell.h"
#import "TGAttachmentGifCell.h"
#import "TGMediaAssetsLibrary.h"
#import "TGMediaAssetFetchResult.h"
#import "TGMediaAssetImageSignals.h"
#import "TGMediaPickerModernGalleryMixin.h"
#import "TGMediaPickerGalleryItem.h"
#import "TGMediaAssetsUtils.h"
#import "TGOverlayControllerWindow.h"
#import "TGMediaAvatarEditorTransition.h"
#import "TGPhotoEditorController.h"
#import "TGVideoEditAdjustments.h"
#import "TGMediaAsset+TGMediaEditableItem.h"
const NSInteger TGAttachmentCameraCellIndex = -1;
const CGSize TGAttachmentCellSize = { 84.0f, 84.0f };
const CGFloat TGAttachmentEdgeInset = 8.0f;
const CGFloat TGAttachmentCarouselHeight = 214.0f;
const CGFloat TGAttachmentCarouselCondensedHeight = 157.0f;
const CGFloat TGAttachmentCarouselCorrection = -114.0f;
const CGFloat TGAttachmentCarouselCondensedCorrection = -57.0f;
const CGFloat TGAttachmentZoomedPhotoHeight = 198.0f;
const CGFloat TGAttachmentZoomedPhotoMaxWidth = 250.0f;
const CGFloat TGAttachmentZoomedPhotoCondensedHeight = 141.0f;
const CGFloat TGAttachmentZoomedPhotoCondensedMaxWidth = 178.0f;
const CGFloat TGAttachmentZoomedPhotoAspectRatio = 1.2626f;
const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
@interface TGAttachmentCarouselItemView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
{
TGMediaAssetsLibrary *_assetsLibrary;
SMetaDisposable *_assetsDisposable;
TGMediaAssetFetchResult *_fetchResult;
bool _forProfilePhoto;
SMetaDisposable *_selectionChangedDisposable;
SMetaDisposable *_itemsSizeChangedDisposable;
UICollectionViewFlowLayout *_smallLayout;
UICollectionViewFlowLayout *_largeLayout;
UICollectionView *_collectionView;
TGMediaAssetsPreheatMixin *_preheatMixin;
TGAttachmentCameraView *_cameraView;
TGMenuSheetButtonItemView *_sendMediaItemView;
TGMenuSheetButtonItemView *_sendFileItemView;
TGMediaPickerModernGalleryMixin *_galleryMixin;
TGMediaAsset *_hiddenItem;
bool _zoomedIn;
bool _zoomingIn;
CGFloat _zoomingProgress;
NSInteger _pivotInItemIndex;
NSInteger _pivotOutItemIndex;
CGSize _imageSize;
CGSize _maxPhotoSize;
CGFloat _carouselHeight;
CGFloat _smallActivationHeight;
CGSize _smallMaxPhotoSize;
CGFloat _smallCarouselHeight;
bool _smallActivated;
CGFloat _carouselCorrection;
}
@end
@implementation TGAttachmentCarouselItemView
- (instancetype)initWithCamera:(bool)hasCamera selfPortrait:(bool)selfPortrait forProfilePhoto:(bool)forProfilePhoto assetType:(TGMediaAssetType)assetType
{
self = [super initWithType:TGMenuSheetItemTypeDefault];
if (self != nil)
{
__weak TGAttachmentCarouselItemView *weakSelf = self;
_forProfilePhoto = forProfilePhoto;
_assetsLibrary = [TGMediaAssetsLibrary libraryForAssetType:assetType];
_assetsDisposable = [[SMetaDisposable alloc] init];
if (!forProfilePhoto)
{
_selectionContext = [[TGMediaSelectionContext alloc] init];
[_selectionContext setItemSourceUpdatedSignal:[_assetsLibrary libraryChanged]];
_selectionContext.updatedItemsSignal = ^SSignal *(NSArray *items)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
return [strongSelf->_assetsLibrary updatedAssetsForAssets:items];
};
_selectionChangedDisposable = [[SMetaDisposable alloc] init];
[_selectionChangedDisposable setDisposable:[[[_selectionContext selectionChangedSignal] mapToSignal:^SSignal *(id value)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return [SSignal complete];
return [[strongSelf->_collectionView noOngoingTransitionSignal] then:[SSignal single:value]];
}] startWithNext:^(__unused TGMediaSelectionChange *change)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
NSInteger index = [strongSelf->_fetchResult indexOfAsset:(TGMediaAsset *)change.item];
[strongSelf updateSendButtonsFromIndex:index];
}]];
_editingContext = [[TGMediaEditingContext alloc] init];
_itemsSizeChangedDisposable = [[SMetaDisposable alloc] init];
[_itemsSizeChangedDisposable setDisposable:[[[_editingContext cropAdjustmentsUpdatedSignal] deliverOn:[SQueue mainQueue]] startWithNext:^(__unused id next)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_zoomedIn)
{
[strongSelf->_largeLayout invalidateLayout];
[strongSelf->_collectionView layoutSubviews];
UICollectionViewCell *pivotCell = (UICollectionViewCell *)[strongSelf->_galleryMixin currentReferenceView];
if (pivotCell != nil)
{
NSIndexPath *indexPath = [strongSelf->_collectionView indexPathForCell:pivotCell];
if (indexPath != nil)
[strongSelf centerOnItemWithIndex:indexPath.row animated:false];
}
}
}]];
}
_smallLayout = [[UICollectionViewFlowLayout alloc] init];
_smallLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_smallLayout.minimumLineSpacing = TGAttachmentEdgeInset;
_largeLayout = [[UICollectionViewFlowLayout alloc] init];
_largeLayout.scrollDirection = _smallLayout.scrollDirection;
_largeLayout.minimumLineSpacing = _smallLayout.minimumLineSpacing;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, _smallLayout.minimumLineSpacing, self.bounds.size.width, TGAttachmentZoomedPhotoHeight) collectionViewLayout:_smallLayout];
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.showsHorizontalScrollIndicator = false;
_collectionView.showsVerticalScrollIndicator = false;
[_collectionView registerClass:[TGAttachmentPhotoCell class] forCellWithReuseIdentifier:TGAttachmentPhotoCellIdentifier];
[_collectionView registerClass:[TGAttachmentVideoCell class] forCellWithReuseIdentifier:TGAttachmentVideoCellIdentifier];
[_collectionView registerClass:[TGAttachmentGifCell class] forCellWithReuseIdentifier:TGAttachmentGifCellIdentifier];
[self addSubview:_collectionView];
if (hasCamera)
{
_cameraView = [[TGAttachmentCameraView alloc] initForSelfPortrait:selfPortrait];
_cameraView.frame = CGRectMake(_smallLayout.minimumLineSpacing, 0, TGAttachmentCellSize.width, TGAttachmentCellSize.height);
[_cameraView startPreview];
[_collectionView addSubview:_cameraView];
_cameraView.pressed = ^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf.superview bringSubviewToFront:strongSelf];
if (strongSelf.cameraPressed != nil)
strongSelf.cameraPressed(strongSelf->_cameraView);
};
}
_sendMediaItemView = [[TGMenuSheetButtonItemView alloc] initWithTitle:nil type:TGMenuSheetButtonTypeSend action:^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf != nil && strongSelf.sendPressed != nil)
strongSelf.sendPressed(nil, false);
}];
[_sendMediaItemView setHidden:true animated:false];
[self addSubview:_sendMediaItemView];
_sendFileItemView = [[TGMenuSheetButtonItemView alloc] initWithTitle:nil type:TGMenuSheetButtonTypeDefault action:^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf != nil && strongSelf.sendPressed != nil)
strongSelf.sendPressed(nil, true);
}];
_sendFileItemView.requiresDivider = false;
[_sendFileItemView setHidden:true animated:false];
[self addSubview:_sendFileItemView];
[self setSignal:[[TGMediaAssetsLibrary authorizationStatusSignal] mapToSignal:^SSignal *(NSNumber *statusValue)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return [SSignal complete];
TGMediaLibraryAuthorizationStatus status = statusValue.int32Value;
if (status == TGMediaLibraryAuthorizationStatusAuthorized)
{
return [[strongSelf->_assetsLibrary cameraRollGroup] mapToSignal:^SSignal *(TGMediaAssetGroup *cameraRollGroup)
{
return [strongSelf->_assetsLibrary assetsOfAssetGroup:cameraRollGroup reversed:true];
}];
}
else
{
return [SSignal fail:nil];
}
}]];
_preheatMixin = [[TGMediaAssetsPreheatMixin alloc] initWithCollectionView:_collectionView scrollDirection:UICollectionViewScrollDirectionHorizontal];
_preheatMixin.imageType = TGMediaAssetImageTypeThumbnail;
_preheatMixin.assetCount = ^NSInteger
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return 0;
return [strongSelf collectionView:strongSelf->_collectionView numberOfItemsInSection:0];
};
_preheatMixin.assetAtIndex = ^TGMediaAsset *(NSInteger index)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
return [strongSelf->_fetchResult assetAtIndex:index];
};
[self _updateImageSize];
_preheatMixin.imageSize = _imageSize;
[self setCondensed:false];
_pivotInItemIndex = NSNotFound;
_pivotOutItemIndex = NSNotFound;
}
return self;
}
- (void)dealloc
{
[_assetsDisposable dispose];
[_selectionChangedDisposable dispose];
[_itemsSizeChangedDisposable dispose];
}
- (void)setCondensed:(bool)condensed
{
_condensed = condensed;
CGFloat delta = -423;
if (condensed)
{
_maxPhotoSize = CGSizeMake(TGAttachmentZoomedPhotoCondensedMaxWidth, TGAttachmentZoomedPhotoCondensedHeight);
_carouselHeight = TGAttachmentCarouselCondensedHeight;
_carouselCorrection = TGAttachmentCarouselCondensedCorrection;
delta += 48;
}
else
{
_maxPhotoSize = CGSizeMake(TGAttachmentZoomedPhotoMaxWidth, TGAttachmentZoomedPhotoHeight);
_carouselHeight = TGAttachmentCarouselHeight;
_carouselCorrection = TGAttachmentCarouselCorrection;
}
CGSize screenSize = TGScreenSize();
_smallActivationHeight = screenSize.width;
CGFloat screenDelta = screenSize.width + delta;
_smallCarouselHeight = MAX(111, _carouselHeight + screenDelta);
CGFloat smallHeight = MAX(95, _maxPhotoSize.height + screenDelta);
_smallMaxPhotoSize = CGSizeMake(ceil(smallHeight * TGAttachmentZoomedPhotoAspectRatio), smallHeight);
CGRect frame = _collectionView.frame;
frame.size.height = _maxPhotoSize.height;
_collectionView.frame = frame;
}
- (void)setSignal:(SSignal *)signal
{
__weak TGAttachmentCarouselItemView *weakSelf = self;
[_assetsDisposable setDisposable:[[[signal mapToSignal:^SSignal *(id value)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return [SSignal complete];
return [[strongSelf->_collectionView noOngoingTransitionSignal] then:[SSignal single:value]];
}] deliverOn:[SQueue mainQueue]] startWithNext:^(id next)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([next isKindOfClass:[TGMediaAssetFetchResult class]])
{
TGMediaAssetFetchResult *fetchResult = (TGMediaAssetFetchResult *)next;
strongSelf->_fetchResult = fetchResult;
[strongSelf->_collectionView reloadData];
}
else if ([next isKindOfClass:[TGMediaAssetFetchResultChange class]])
{
TGMediaAssetFetchResultChange *change = (TGMediaAssetFetchResultChange *)next;
strongSelf->_fetchResult = change.fetchResultAfterChanges;
[TGMediaAssetsCollectionViewIncrementalUpdater updateCollectionView:strongSelf->_collectionView withChange:change completion:nil];
dispatch_async(dispatch_get_main_queue(), ^
{
[strongSelf scrollViewDidScroll:strongSelf->_collectionView];
});
}
if (strongSelf->_galleryMixin != nil && strongSelf->_fetchResult != nil)
[strongSelf->_galleryMixin updateWithFetchResult:strongSelf->_fetchResult];
}]];
}
- (SSignal *)_signalForItem:(TGMediaAsset *)asset
{
return [self _signalForItem:asset refresh:false onlyThumbnail:false];
}
- (SSignal *)_signalForItem:(TGMediaAsset *)asset refresh:(bool)refresh onlyThumbnail:(bool)onlyThumbnail
{
bool thumbnail = onlyThumbnail || !_zoomedIn;
CGSize imageSize = onlyThumbnail ? [self imageSizeForThumbnail:true] : _imageSize;
TGMediaAssetImageType screenImageType = refresh ? TGMediaAssetImageTypeLargeThumbnail : TGMediaAssetImageTypeFastLargeThumbnail;
TGMediaAssetImageType imageType = thumbnail ? TGMediaAssetImageTypeAspectRatioThumbnail : screenImageType;
SSignal *assetSignal = [TGMediaAssetImageSignals imageForAsset:asset imageType:imageType size:imageSize];
if (_editingContext == nil)
return assetSignal;
SSignal *editedSignal = thumbnail ? [_editingContext thumbnailImageSignalForItem:asset] : [_editingContext fastImageSignalForItem:asset withUpdates:true];
return [editedSignal mapToSignal:^SSignal *(id result)
{
if (result != nil)
return [SSignal single:result];
else
return assetSignal;
}];
}
#pragma mark -
- (void)setCameraZoomedIn:(bool)zoomedIn progress:(CGFloat)progress
{
if (_cameraView == nil)
return;
CGFloat size = TGAttachmentCellSize.height;
progress = zoomedIn ? progress : 1.0f - progress;
_cameraView.frame = CGRectMake(_smallLayout.minimumLineSpacing - (size + _smallLayout.minimumLineSpacing) * progress, 0, TGAttachmentCellSize.width + (size - TGAttachmentCellSize.width) * progress, TGAttachmentCellSize.height + (size - TGAttachmentCellSize.height) * progress);
[_cameraView setZoomedProgress:progress];
}
- (void)setZoomedMode:(bool)zoomed animated:(bool)animated index:(NSInteger)index
{
if (zoomed == _zoomedIn)
{
if (_zoomedIn)
[self centerOnItemWithIndex:index animated:animated];
return;
}
_zoomedIn = zoomed;
_zoomingIn = true;
_collectionView.userInteractionEnabled = false;
if (zoomed)
_pivotInItemIndex = index;
else
_pivotOutItemIndex = index;
UICollectionViewFlowLayout *toLayout = _zoomedIn ? _largeLayout : _smallLayout;
[self _updateImageSize];
__weak TGAttachmentCarouselItemView *weakSelf = self;
TGTransitionLayout *layout = (TGTransitionLayout *)[_collectionView transitionToCollectionViewLayout:toLayout duration:0.3f completion:^(__unused BOOL completed, __unused BOOL finished)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_zoomingIn = false;
strongSelf->_collectionView.userInteractionEnabled = true;
[strongSelf centerOnItemWithIndex:index animated:false];
}];
layout.progressChanged = ^(CGFloat progress)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_zoomingProgress = progress;
[strongSelf requestMenuLayoutUpdate];
[strongSelf _layoutButtonItemViews];
[strongSelf setCameraZoomedIn:strongSelf->_zoomedIn progress:progress];
};
layout.transitionAlmostFinished = ^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_pivotInItemIndex = NSNotFound;
strongSelf->_pivotOutItemIndex = NSNotFound;
};
CGPoint toOffset = [_collectionView toContentOffsetForLayout:layout indexPath:[NSIndexPath indexPathForRow:index inSection:0] toSize:_collectionView.bounds.size toContentInset:[self collectionView:_collectionView layout:toLayout insetForSectionAtIndex:0]];
layout.toContentOffset = toOffset;
for (TGMenuSheetItemView *itemView in self.underlyingViews)
[itemView setHidden:zoomed animated:animated];
[_sendMediaItemView setHidden:!zoomed animated:animated];
[_sendFileItemView setHidden:!zoomed animated:animated];
[self _updateVisibleItems];
}
- (void)updateSendButtonsFromIndex:(NSInteger)index
{
__block NSInteger photosCount = 0;
__block NSInteger videosCount = 0;
__block NSInteger gifsCount = 0;
[_selectionContext enumerateSelectedItems:^(id<TGMediaSelectableItem> item)
{
TGMediaAsset *asset = (TGMediaAsset *)item;
if (![asset isKindOfClass:[TGMediaAsset class]])
return;
switch (asset.type)
{
case TGMediaAssetVideoType:
videosCount++;
break;
case TGMediaAssetGifType:
gifsCount++;
break;
default:
photosCount++;
break;
}
}];
NSInteger totalCount = photosCount + videosCount + gifsCount;
bool activated = (totalCount > 0);
if ([self zoomedModeSupported])
[self setZoomedMode:activated animated:true index:index];
else
[self setSelectedMode:activated animated:true];
if (totalCount == 0)
return;
if (photosCount > 0 && videosCount == 0 && gifsCount == 0)
{
NSString *format = TGLocalized([TGStringUtils integerValueFormat:@"AttachmentMenu.SendPhoto_" value:photosCount]);
_sendMediaItemView.title = [NSString stringWithFormat:format, [NSString stringWithFormat:@"%ld", photosCount]];
}
else if (videosCount > 0 && photosCount == 0 && gifsCount == 0)
{
NSString *format = TGLocalized([TGStringUtils integerValueFormat:@"AttachmentMenu.SendVideo_" value:videosCount]);
_sendMediaItemView.title = [NSString stringWithFormat:format, [NSString stringWithFormat:@"%ld", videosCount]];
}
else if (gifsCount > 0 && photosCount == 0 && videosCount == 0)
{
NSString *format = TGLocalized([TGStringUtils integerValueFormat:@"AttachmentMenu.SendGif_" value:gifsCount]);
_sendMediaItemView.title = [NSString stringWithFormat:format, [NSString stringWithFormat:@"%ld", gifsCount]];
}
else
{
NSString *format = TGLocalized([TGStringUtils integerValueFormat:@"AttachmentMenu.SendItem_" value:totalCount]);
_sendMediaItemView.title = [NSString stringWithFormat:format, [NSString stringWithFormat:@"%ld", totalCount]];
}
if (totalCount == 1)
_sendFileItemView.title = TGLocalized(@"AttachmentMenu.SendAsFile");
else
_sendFileItemView.title = TGLocalized(@"AttachmentMenu.SendAsFiles");
}
- (void)setSelectedMode:(bool)selected animated:(bool)animated
{
[self.underlyingViews.firstObject setHidden:selected animated:animated];
[_sendMediaItemView setHidden:!selected animated:animated];
}
- (bool)zoomedModeSupported
{
return [TGMediaAssetsLibrary usesPhotoFramework];
}
- (CGPoint)contentOffsetForItemAtIndex:(NSInteger)index
{
CGRect cellFrame = [_collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]].frame;
CGFloat x = cellFrame.origin.x - (_collectionView.frame.size.width - cellFrame.size.width) / 2.0f;
CGFloat contentOffset = MAX(0.0f, MIN(x, _collectionView.contentSize.width - _collectionView.frame.size.width));
return CGPointMake(contentOffset, 0);
}
- (void)centerOnItemWithIndex:(NSInteger)index animated:(bool)animated
{
[_collectionView setContentOffset:[self contentOffsetForItemAtIndex:index] animated:animated];
}
#pragma mark -
- (CGFloat)_preferredHeightForZoomedIn:(bool)zoomedIn progress:(CGFloat)progress screenHeight:(CGFloat)screenHeight
{
progress = zoomedIn ? progress : 1.0f - progress;
CGFloat carouselHeight = _carouselHeight;
if (_sizeClass == UIUserInterfaceSizeClassCompact && fabs(screenHeight - _smallActivationHeight) < FLT_EPSILON)
carouselHeight = _smallCarouselHeight;
return 100.0f + (carouselHeight - 100.0f) * progress;
}
- (CGFloat)_heightCorrectionForZoomedIn:(bool)zoomedIn progress:(CGFloat)progress
{
progress = zoomedIn ? progress : 1.0f - progress;
return _carouselCorrection * progress;
}
- (CGFloat)preferredHeightForWidth:(CGFloat)__unused width screenHeight:(CGFloat)screenHeight
{
CGFloat progress = _zoomingIn ? _zoomingProgress : 1.0f;
return [self _preferredHeightForZoomedIn:_zoomedIn progress:progress screenHeight:screenHeight];
}
- (CGFloat)contentHeightCorrection
{
CGFloat progress = _zoomingIn ? _zoomingProgress : 1.0f;
return [self _heightCorrectionForZoomedIn:_zoomedIn progress:progress];
}
#pragma mark -
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (!_sendMediaItemView.userInteractionEnabled)
return [super pointInside:point withEvent:event];
return CGRectContainsPoint(self.bounds, point) || CGRectContainsPoint(_sendMediaItemView.frame, point) || CGRectContainsPoint(_sendFileItemView.frame, point);
}
#pragma mark -
- (void)_updateVisibleItems
{
for (NSIndexPath *indexPath in _collectionView.indexPathsForVisibleItems)
{
TGMediaAsset *asset = [_fetchResult assetAtIndex:indexPath.row];
TGAttachmentAssetCell *cell = (TGAttachmentAssetCell *)[_collectionView cellForItemAtIndexPath:indexPath];
if (cell.isZoomed != _zoomedIn)
{
cell.isZoomed = _zoomedIn;
[cell setSignal:[self _signalForItem:asset refresh:true onlyThumbnail:false]];
}
}
}
- (void)_updateImageSize
{
_imageSize = [self imageSizeForThumbnail:!_zoomedIn];
}
- (CGSize)imageSizeForThumbnail:(bool)forThumbnail
{
CGFloat scale = MIN(2.0f, TGScreenScaling());
if (forThumbnail)
return CGSizeMake(TGAttachmentCellSize.width * scale, TGAttachmentCellSize.height * scale);
else
return CGSizeMake(floor(TGAttachmentZoomedPhotoMaxWidth * scale), floor(TGAttachmentZoomedPhotoMaxWidth * scale));
}
- (bool)hasCameraInCurrentMode
{
return (!_zoomedIn && _cameraView != nil);
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = indexPath.row;
TGMediaAsset *asset = [_fetchResult assetAtIndex:index];
__block UIImage *thumbnailImage = nil;
if ([TGMediaAssetsLibrary usesPhotoFramework])
{
TGAttachmentAssetCell *cell = (TGAttachmentAssetCell *)[collectionView cellForItemAtIndexPath:indexPath];
if ([cell isKindOfClass:[TGAttachmentAssetCell class]])
thumbnailImage = cell.imageView.image;
}
else
{
[[TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeAspectRatioThumbnail size:CGSizeZero] startWithNext:^(UIImage *next)
{
thumbnailImage = next;
}];
}
__weak TGAttachmentCarouselItemView *weakSelf = self;
UIView *(^referenceViewForAsset)(TGMediaAsset *) = ^UIView *(TGMediaAsset *asset)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
for (TGAttachmentAssetCell *cell in [strongSelf->_collectionView visibleCells])
{
if ([cell.asset isEqual:asset])
return cell;
}
return nil;
};
if (self.openEditor)
{
TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithItem:asset intent:TGPhotoEditorControllerAvatarIntent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab];
controller.dontHideStatusBar = true;
TGMediaAvatarEditorTransition *transition = [[TGMediaAvatarEditorTransition alloc] initWithController:controller fromView:referenceViewForAsset(asset)];
controller.didFinishRenderingFullSizeImage = ^(UIImage *resultImage)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[[strongSelf->_assetsLibrary saveAssetWithImage:resultImage] startWithNext:nil];
};
__weak TGPhotoEditorController *weakController = controller;
controller.didFinishEditing = ^(__unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges)
{
if (!hasChanges)
return;
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
__strong TGPhotoEditorController *strongController = weakController;
if (strongController == nil)
return;
if (strongSelf.avatarCompletionBlock != nil)
strongSelf.avatarCompletionBlock(resultImage);
[strongController dismissAnimated:true];
};
controller.requestThumbnailImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];
};
controller.requestOriginalScreenSizeImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem screenImageSignal];
};
controller.requestOriginalFullSizeImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem originalImageSignal];
};
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithParentController:_parentController contentController:controller];
controllerWindow.hidden = false;
controller.view.clipsToBounds = true;
transition.referenceFrame = ^CGRect
{
UIView *referenceView = referenceViewForAsset(asset);
return [referenceView.superview convertRect:referenceView.frame toView:nil];
};
transition.referenceImageSize = ^CGSize
{
return asset.dimensions;
};
transition.referenceScreenImageSignal = ^SSignal *
{
return [TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeFastScreen size:CGSizeMake(640, 640)];
};
[transition presentAnimated:true];
controller.beginCustomTransitionOut = ^(CGRect outReferenceFrame, UIView *repView, void (^completion)(void))
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
transition.outReferenceFrame = outReferenceFrame;
transition.repView = repView;
[transition dismissAnimated:true completion:^
{
strongSelf->_hiddenItem = nil;
[strongSelf updateHiddenCellAnimated:false];
dispatch_async(dispatch_get_main_queue(), ^
{
if (completion != nil)
completion();
});
}];
};
_hiddenItem = asset;
[self updateHiddenCellAnimated:false];
}
else
{
_galleryMixin = [[TGMediaPickerModernGalleryMixin alloc] initWithItem:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaption:_allowCaptions && !_forProfilePhoto asFile:false itemsLimit:TGAttachmentDisplayedAssetLimit];
_galleryMixin.thumbnailSignalForItem = ^SSignal *(id item)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
return [strongSelf _signalForItem:item refresh:false onlyThumbnail:true];
};
_galleryMixin.referenceViewForItem = ^UIView *(TGMediaPickerGalleryItem *item)
{
return referenceViewForAsset(item.asset);
};
_galleryMixin.itemFocused = ^(TGMediaPickerGalleryItem *item)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_hiddenItem = item.asset;
[strongSelf updateHiddenCellAnimated:false];
};
_galleryMixin.willTransitionIn = ^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf.superview bringSubviewToFront:strongSelf];
[strongSelf->_cameraView pausePreview];
};
_galleryMixin.willTransitionOut = ^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_cameraView resumePreview];
};
_galleryMixin.didTransitionOut = ^
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_hiddenItem = nil;
[strongSelf updateHiddenCellAnimated:true];
strongSelf->_galleryMixin = nil;
};
_galleryMixin.completeWithItem = ^(TGMediaPickerGalleryItem *item)
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf != nil && strongSelf.sendPressed != nil)
strongSelf.sendPressed(item.asset, false);
};
_galleryMixin.editorOpened = self.editorOpened;
_galleryMixin.editorClosed = self.editorClosed;
[_galleryMixin present];
}
}
- (NSInteger)collectionView:(UICollectionView *)__unused collectionView numberOfItemsInSection:(NSInteger)__unused section
{
return MIN(_fetchResult.count, TGAttachmentDisplayedAssetLimit);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index = indexPath.row;
TGMediaAsset *asset = [_fetchResult assetAtIndex:index];
NSString *cellIdentifier = nil;
switch (asset.type)
{
case TGMediaAssetVideoType:
cellIdentifier = TGAttachmentVideoCellIdentifier;
break;
case TGMediaAssetGifType:
if (_forProfilePhoto)
cellIdentifier = TGAttachmentPhotoCellIdentifier;
else
cellIdentifier = TGAttachmentGifCellIdentifier;
break;
default:
cellIdentifier = TGAttachmentPhotoCellIdentifier;
break;
}
TGAttachmentAssetCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSInteger pivotIndex = NSNotFound;
NSInteger limit = 0;
if (_pivotInItemIndex != NSNotFound)
{
if (self.frame.size.width <= 320)
limit = 2;
else
limit = 3;
pivotIndex = _pivotInItemIndex;
}
else if (_pivotOutItemIndex != NSNotFound)
{
pivotIndex = _pivotOutItemIndex;
if (self.frame.size.width <= 320)
limit = 3;
else
limit = 5;
}
if (!(pivotIndex != NSNotFound && (indexPath.row < pivotIndex - limit || indexPath.row > pivotIndex + limit)))
{
cell.selectionContext = _selectionContext;
cell.editingContext = _editingContext;
if (![asset isEqual:cell.asset] || cell.isZoomed != _zoomedIn)
{
cell.isZoomed = _zoomedIn;
[cell setAsset:asset signal:[self _signalForItem:asset refresh:[cell.asset isEqual:asset] onlyThumbnail:false]];
}
}
return cell;
}
- (void)updateHiddenCellAnimated:(bool)animated
{
for (TGAttachmentAssetCell *cell in [_collectionView visibleCells])
[cell setHidden:([cell.asset isEqual:_hiddenItem]) animated:animated];
}
#pragma mark -
- (CGSize)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_zoomedIn)
{
CGSize maxPhotoSize = _maxPhotoSize;
if (_smallActivated)
maxPhotoSize = _smallMaxPhotoSize;
if (_pivotInItemIndex != NSNotFound && (indexPath.row < _pivotInItemIndex - 2 || indexPath.row > _pivotInItemIndex + 2))
return CGSizeMake(maxPhotoSize.height, maxPhotoSize.height);
TGMediaAsset *asset = [_fetchResult assetAtIndex:indexPath.row];
if (asset != nil)
{
CGSize dimensions = asset.dimensions;
if (dimensions.width < 1.0f)
dimensions.width = 1.0f;
if (dimensions.height < 1.0f)
dimensions.height = 1.0f;
id<TGMediaEditAdjustments> adjustments = [_editingContext adjustmentsForItem:asset];
if ([adjustments cropAppliedForAvatar:false] || ([adjustments isKindOfClass:[TGVideoEditAdjustments class]] && [(TGVideoEditAdjustments *)adjustments rotationApplied]))
{
dimensions = adjustments.cropRect.size;
bool sideward = TGOrientationIsSideward(adjustments.cropOrientation, NULL);
if (sideward)
dimensions = CGSizeMake(dimensions.height, dimensions.width);
}
CGFloat width = MIN(maxPhotoSize.width, ceil(dimensions.width * maxPhotoSize.height / dimensions.height));
return CGSizeMake(width, maxPhotoSize.height);
}
return CGSizeMake(maxPhotoSize.height, maxPhotoSize.height);
}
return TGAttachmentCellSize;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout insetForSectionAtIndex:(NSInteger)__unused section
{
CGFloat edgeInset = _smallLayout.minimumLineSpacing;
CGFloat leftInset = [self hasCameraInCurrentMode] ? 2 * edgeInset + 84.0f : edgeInset;
CGFloat additionalInset = _smallActivated ? _maxPhotoSize.height - _smallMaxPhotoSize.height : 0.0f;
CGFloat bottomInset = _zoomedIn ? 0.0f : -([self _heightCorrectionForZoomedIn:true progress:1.0f] + additionalInset);
return UIEdgeInsetsMake(0, leftInset, bottomInset, edgeInset);
}
- (CGFloat)collectionView:(UICollectionView *)__unused collectionView layout:(UICollectionViewLayout *)__unused collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)__unused section
{
return _smallLayout.minimumLineSpacing;
}
- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)__unused collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout
{
return [[TGTransitionLayout alloc] initWithCurrentLayout:fromLayout nextLayout:toLayout];
}
#pragma mark -
- (void)scrollViewDidScroll:(UIScrollView *)__unused scrollView
{
if (_zoomingIn)
return;
if (!_zoomedIn)
[_preheatMixin update];
for (UICollectionViewCell *cell in _collectionView.visibleCells)
{
if ([cell isKindOfClass:[TGAttachmentAssetCell class]])
[(TGAttachmentAssetCell *)cell setNeedsLayout];
}
}
#pragma mark -
- (void)menuView:(TGMenuSheetView *)menuView willAppearAnimated:(bool)__unused animated
{
__weak TGAttachmentCarouselItemView *weakSelf = self;
menuView.tapDismissalAllowed = ^bool
{
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return true;
return !strongSelf->_collectionView.isDecelerating && !strongSelf->_collectionView.isTracking;
};
}
- (void)menuView:(TGMenuSheetView *)menuView willDisappearAnimated:(bool)animated
{
[super menuView:menuView didDisappearAnimated:animated];
menuView.tapDismissalAllowed = nil;
[_cameraView stopPreview];
}
#pragma mark -
- (void)setScreenHeight:(CGFloat)screenHeight
{
_screenHeight = screenHeight;
[self _updateSmallActivated];
}
- (void)setSizeClass:(UIUserInterfaceSizeClass)sizeClass
{
_sizeClass = sizeClass;
[self _updateSmallActivated];
}
- (void)_updateSmallActivated
{
_smallActivated = (fabs(_screenHeight - _smallActivationHeight) < FLT_EPSILON && _sizeClass == UIUserInterfaceSizeClassCompact);
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect frame = _collectionView.frame;
frame.size.width = self.frame.size.width;
frame.size.height = _smallActivated ? _smallMaxPhotoSize.height : _maxPhotoSize.height;
if (!CGRectEqualToRect(frame, _collectionView.frame))
{
_collectionView.frame = frame;
[_smallLayout invalidateLayout];
[_largeLayout invalidateLayout];
}
[self _layoutButtonItemViews];
}
- (void)_layoutButtonItemViews
{
_sendMediaItemView.frame = CGRectMake(0, [self preferredHeightForWidth:self.frame.size.width screenHeight:self.screenHeight], self.frame.size.width, [_sendMediaItemView preferredHeightForWidth:self.frame.size.width screenHeight:self.screenHeight]);
_sendFileItemView.frame = CGRectMake(0, CGRectGetMaxY(_sendMediaItemView.frame), self.frame.size.width, [_sendFileItemView preferredHeightForWidth:self.frame.size.width screenHeight:self.screenHeight]);
}
@end