#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 () { 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 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 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 editableItem) { return [editableItem thumbnailImageSignal]; }; controller.requestOriginalScreenSizeImage = ^(id editableItem) { return [editableItem screenImageSignal]; }; controller.requestOriginalFullSizeImage = ^(id 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 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