#import "TGAttachmentCarouselItemView.h" #import "LegacyComponentsInternal.h" #import #import #import #import #import #import #import "TGTransitionLayout.h" #import "TGAttachmentCameraView.h" #import "TGAttachmentPhotoCell.h" #import "TGAttachmentVideoCell.h" #import "TGAttachmentGifCell.h" #import #import #import #import #import #import #import "TGMediaAvatarEditorTransition.h" #import #import #import const CGSize TGAttachmentCellSize = { 84.0f, 84.0f }; const CGFloat TGAttachmentEdgeInset = 8.0f; const CGFloat TGAttachmentZoomedPhotoRemainer = 32.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 TGAttachmentCarouselCollectionView () @end @implementation TGAttachmentCarouselCollectionView - (bool)disableGalleryTransitionOffsetFix { return true; } @end @interface TGAttachmentCarouselItemView () { TGMediaAssetsLibrary *_assetsLibrary; SMetaDisposable *_assetsDisposable; TGMediaAssetFetchResult *_fetchResult; bool _document; bool _forProfilePhoto; SMetaDisposable *_selectionChangedDisposable; SMetaDisposable *_itemsSizeChangedDisposable; UICollectionViewFlowLayout *_smallLayout; UICollectionViewFlowLayout *_largeLayout; UICollectionView *_collectionView; TGMediaAssetsPreheatMixin *_preheatMixin; TGAttachmentCameraView *_cameraView; TGMenuSheetButtonItemView *_sendMediaItemView; TGMenuSheetButtonItemView *_sendFileItemView; TGMediaPickerModernGalleryMixin *_galleryMixin; TGMediaPickerModernGalleryMixin *_previewGalleryMixin; TGMediaAsset *_hiddenItem; bool _zoomedIn; bool _zoomingIn; CGFloat _zoomingProgress; NSInteger _pivotInItemIndex; NSInteger _pivotOutItemIndex; CGSize _imageSize; CGSize _maxPhotoSize; CGFloat _smallActivationHeight; bool _smallActivated; CGSize _smallMaxPhotoSize; CGFloat _carouselCorrection; id _context; bool _saveEditedPhotos; TGMenuSheetPallete *_pallete; } @end @implementation TGAttachmentCarouselItemView - (bool)disableGalleryTransitionOffsetFix { return true; } - (instancetype)initWithContext:(id)context camera:(bool)hasCamera selfPortrait:(bool)selfPortrait forProfilePhoto:(bool)forProfilePhoto assetType:(TGMediaAssetType)assetType saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping { return [self initWithContext:context camera:hasCamera selfPortrait:selfPortrait forProfilePhoto:forProfilePhoto assetType:assetType saveEditedPhotos:saveEditedPhotos allowGrouping:allowGrouping allowSelection:true allowEditing:!selfPortrait document:false selectionLimit:30]; } - (instancetype)initWithContext:(id)context camera:(bool)hasCamera selfPortrait:(bool)selfPortrait forProfilePhoto:(bool)forProfilePhoto assetType:(TGMediaAssetType)assetType saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping allowSelection:(bool)allowSelection allowEditing:(bool)allowEditing document:(bool)document selectionLimit:(int)selectionLimit { self = [super initWithType:TGMenuSheetItemTypeDefault]; if (self != nil) { _context = context; _saveEditedPhotos = saveEditedPhotos; #if TARGET_IPHONE_SIMULATOR hasCamera = true; #endif __weak TGAttachmentCarouselItemView *weakSelf = self; _document = document; _forProfilePhoto = forProfilePhoto; _assetsLibrary = [TGMediaAssetsLibrary libraryForAssetType:assetType]; _assetsDisposable = [[SMetaDisposable alloc] init]; if (!forProfilePhoto && allowSelection) { _selectionContext = [[TGMediaSelectionContext alloc] initWithGroupingAllowed:allowGrouping selectionLimit:selectionLimit]; _selectionContext.selectionLimitExceeded = ^{ __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) { return; } if (strongSelf->_selectionLimitExceeded) { strongSelf->_selectionLimitExceeded(); } }; if (allowGrouping) _selectionContext.grouping = true; [_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 = 0; if ([change.item isKindOfClass:[TGCameraCapturedVideo class]]) { index = [strongSelf->_fetchResult indexOfAsset:((TGCameraCapturedVideo *)change.item).originalAsset]; } else { index = [strongSelf->_fetchResult indexOfAsset:(TGMediaAsset *)change.item]; } [strongSelf updateSendButtonsFromIndex:index]; [strongSelf updateSelectionIndexes]; }]]; } if (allowEditing) { _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; if (hasCamera) { _cameraView = [[TGAttachmentCameraView alloc] initForSelfPortrait:selfPortrait]; _cameraView.frame = CGRectMake(_smallLayout.minimumLineSpacing, 0, TGAttachmentCellSize.width, TGAttachmentCellSize.height); [_cameraView startPreview]; _cameraView.pressed = ^ { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf.superview bringSubviewToFront:strongSelf]; if (strongSelf.cameraPressed != nil) strongSelf.cameraPressed(strongSelf->_cameraView); }; } _collectionView = [[TGAttachmentCarouselCollectionView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, TGAttachmentZoomedPhotoHeight + TGAttachmentEdgeInset * 2) collectionViewLayout:_smallLayout]; if (iosMajorVersion() >= 11) _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; _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]; if (iosMajorVersion() >= 9) _collectionView.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight; [self addSubview:_collectionView]; if (_cameraView) [_collectionView addSubview:_cameraView]; _sendMediaItemView = [[TGMenuSheetButtonItemView alloc] initWithTitle:nil type:TGMenuSheetButtonTypeSend fontSize:20.0 action:^ { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil && strongSelf.sendPressed != nil) { if (strongSelf->_selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; strongSelf.sendPressed(nil, false, false, 0); } }]; [_sendMediaItemView setHidden:true animated:false]; [self addSubview:_sendMediaItemView]; if (!_document) { _sendFileItemView = [[TGMenuSheetButtonItemView alloc] initWithTitle:nil type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil && strongSelf.sendPressed != nil) strongSelf.sendPressed(nil, true, false, 0); }]; _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 intValue]; 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.assetAtIndexPath = ^TGMediaAsset *(NSIndexPath *indexPath) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return nil; return [strongSelf->_fetchResult assetAtIndex:indexPath.row]; }; [self _updateImageSize]; _preheatMixin.imageSize = _imageSize; [self setCondensed:false]; _pivotInItemIndex = NSNotFound; _pivotOutItemIndex = NSNotFound; } return self; } - (void)dealloc { [_assetsDisposable dispose]; [_selectionChangedDisposable dispose]; [_itemsSizeChangedDisposable dispose]; } - (void)setPallete:(TGMenuSheetPallete *)pallete { _pallete = pallete; _cameraView.pallete = pallete; [_sendMediaItemView setPallete:pallete]; [_sendFileItemView setPallete:pallete]; } - (void)setRemainingHeight:(CGFloat)remainingHeight { _remainingHeight = remainingHeight; [self setCondensed:_condensed]; } - (void)updateSelectionIndexes { for (TGAttachmentAssetCell *cell in _collectionView.visibleCells) { NSUInteger index = [self.selectionContext indexOfItem:cell.asset]; [cell.checkButton setNumber:index]; } } - (void)setCondensed:(bool)condensed { _condensed = condensed; if (condensed) _maxPhotoSize = CGSizeMake(TGAttachmentZoomedPhotoCondensedMaxWidth, TGAttachmentZoomedPhotoCondensedHeight); else _maxPhotoSize = CGSizeMake(TGAttachmentZoomedPhotoMaxWidth, TGAttachmentZoomedPhotoHeight); if (_remainingHeight > TGMenuSheetButtonItemViewHeight * (condensed ? 3 : 4)) _maxPhotoSize.height += TGAttachmentZoomedPhotoRemainer; CGSize screenSize = TGScreenSize(); _smallActivationHeight = screenSize.width; CGFloat smallHeight = MAX(95, screenSize.width - 225); _smallMaxPhotoSize = CGSizeMake(ceil(smallHeight * TGAttachmentZoomedPhotoAspectRatio), smallHeight); CGRect frame = _collectionView.frame; frame.size.height = _maxPhotoSize.height + TGAttachmentEdgeInset * 2; _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]]; toOffset.y = 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 (_document) { _sendMediaItemView.title = TGLocalized(@"Common.Done"); return; } 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)__unused screenHeight { progress = zoomedIn ? progress : 1.0f - progress; CGFloat inset = TGAttachmentEdgeInset * 2; CGFloat cellHeight = TGAttachmentCellSize.height; CGFloat targetCellHeight = _smallActivated ? _smallMaxPhotoSize.height : _maxPhotoSize.height; cellHeight = cellHeight + (targetCellHeight - cellHeight) * progress; return cellHeight + inset; } - (CGFloat)_heightCorrectionForZoomedIn:(bool)zoomedIn progress:(CGFloat)progress { progress = zoomedIn ? progress : 1.0f - progress; CGFloat correction = self.remainingHeight - (_document ? 1 : 2) * TGMenuSheetButtonItemViewHeight; return -(correction * progress); } - (CGFloat)preferredHeightForWidth:(CGFloat)__unused width screenHeight:(CGFloat)screenHeight { if (_collapsed) { self.alpha = 0.0f; return 0.0f; } 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; if (cell.asset == nil) [cell setAsset:asset signal:[self _signalForItem:asset refresh:true onlyThumbnail:false]]; else [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); } #pragma mark - - (void)_setupGalleryMixin:(TGMediaPickerModernGalleryMixin *)mixin { __weak TGAttachmentCarouselItemView *weakSelf = self; mixin.referenceViewForItem = ^UIView *(TGMediaPickerGalleryItem *item) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil) return [strongSelf referenceViewForAsset:item.asset]; return nil; }; mixin.itemFocused = ^(TGMediaPickerGalleryItem *item) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return; strongSelf->_hiddenItem = item.asset; [strongSelf updateHiddenCellAnimated:false]; }; mixin.willTransitionIn = ^ { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf.superview bringSubviewToFront:strongSelf]; [strongSelf->_cameraView pausePreview]; }; mixin.willTransitionOut = ^ { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return; [strongSelf->_cameraView resumePreview]; }; mixin.didTransitionOut = ^ { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return; strongSelf->_hiddenItem = nil; [strongSelf updateHiddenCellAnimated:true]; strongSelf->_galleryMixin = nil; }; mixin.completeWithItem = ^(TGMediaPickerGalleryItem *item, bool silentPosting, int32_t scheduleTime) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf != nil && strongSelf.sendPressed != nil) { if (strongSelf->_selectionContext.allowGrouping) [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; strongSelf.sendPressed(item.asset, strongSelf.asFile, silentPosting, scheduleTime); } }; mixin.editorOpened = self.editorOpened; mixin.editorClosed = self.editorClosed; } - (TGMediaPickerModernGalleryMixin *)galleryMixinForIndexPath:(NSIndexPath *)indexPath previewMode:(bool)previewMode outAsset:(TGMediaAsset **)outAsset { TGMediaAsset *asset = [_fetchResult assetAtIndex:indexPath.row]; if (outAsset != NULL) *outAsset = asset; UIImage *thumbnailImage = nil; TGAttachmentAssetCell *cell = (TGAttachmentAssetCell *)[_collectionView cellForItemAtIndexPath:indexPath]; if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) thumbnailImage = cell.imageView.image; TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder stickersContext:self.stickersContext]; mixin.presentScheduleController = self.presentScheduleController; mixin.presentTimerController = self.presentTimerController; __weak TGAttachmentCarouselItemView *weakSelf = self; mixin.thumbnailSignalForItem = ^SSignal *(id item) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return nil; return [strongSelf _signalForItem:item refresh:false onlyThumbnail:true]; }; if (!previewMode) [self _setupGalleryMixin:mixin]; return mixin; } - (UIView *)referenceViewForAsset:(TGMediaAsset *)asset { for (TGAttachmentAssetCell *cell in [_collectionView visibleCells]) { if ([cell.asset.uniqueIdentifier isEqual:asset.uniqueIdentifier]) return cell; } return 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 [strongSelf referenceViewForAsset:asset]; return nil; }; if (self.openEditor) { id windowManager = [_context makeOverlayWindowManager]; id editableItem = asset; if (asset.type == TGMediaAssetGifType) { editableItem = [[TGCameraCapturedVideo alloc] initWithAsset:asset livePhoto:false]; } TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:editableItem intent:_disableStickers ? TGPhotoEditorControllerSignupAvatarIntent : TGPhotoEditorControllerAvatarIntent adjustments:nil caption:nil screenImage:thumbnailImage availableTabs:[TGPhotoEditorController defaultTabsForAvatarIntent] selectedTab:TGPhotoEditorCropTab]; controller.editingContext = _editingContext; controller.stickersContext = _stickersContext; controller.dontHideStatusBar = true; TGMediaAvatarEditorTransition *transition = [[TGMediaAvatarEditorTransition alloc] initWithController:controller fromView:referenceViewForAsset(asset)]; controller.didFinishRenderingFullSizeImage = ^(UIImage *resultImage) { __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil || !strongSelf->_saveEditedPhotos) 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); }; controller.didFinishEditingVideo = ^(NSURL *url, id adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) { if (!hasChanges) return; __strong TGAttachmentCarouselItemView *strongSelf = weakSelf; if (strongSelf == nil) return; __strong TGPhotoEditorController *strongController = weakController; if (strongController == nil) return; if (strongSelf.avatarVideoCompletionBlock != nil) strongSelf.avatarVideoCompletionBlock(resultImage, url, adjustments); }; controller.requestThumbnailImage = ^(id editableItem) { return [editableItem thumbnailImageSignal]; }; controller.requestOriginalScreenSizeImage = ^(id editableItem, NSTimeInterval position) { return [editableItem screenImageSignal:position]; }; controller.requestOriginalFullSizeImage = ^(id editableItem, NSTimeInterval position) { if (editableItem.isVideo) { if ([editableItem isKindOfClass:[TGMediaAsset class]]) { return [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)editableItem allowNetworkAccess:true]; } else if ([editableItem isKindOfClass:[TGCameraCapturedVideo class]]) { return ((TGCameraCapturedVideo *)editableItem).avAsset; } else { return [editableItem originalImageSignal:position]; } } else { return [editableItem originalImageSignal:position]; } }; TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:_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:true]; dispatch_async(dispatch_get_main_queue(), ^ { if (completion != nil) completion(); }); }]; }; _hiddenItem = asset; [self updateHiddenCellAnimated:false]; } else { _galleryMixin = [self galleryMixinForIndexPath:indexPath previewMode:false outAsset:NULL]; [_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: cellIdentifier = TGAttachmentGifCellIdentifier; break; default: cellIdentifier = TGAttachmentPhotoCellIdentifier; break; } TGAttachmentAssetCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; cell.pallete = _pallete; 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 = 4; else limit = 6; } cell.selectionContext = _selectionContext; cell.editingContext = _editingContext; if (!(pivotIndex != NSNotFound && (indexPath.row < pivotIndex - limit || indexPath.row > pivotIndex + limit))) { 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]]; } } if (self.selectionContext != nil) [cell.checkButton setNumber:[self.selectionContext indexOfItem:asset]]; return cell; } - (void)updateHiddenCellAnimated:(bool)animated { for (TGAttachmentAssetCell *cell in [_collectionView visibleCells]) [cell setHidden:([cell.asset.uniqueIdentifier isEqual:_hiddenItem.uniqueIdentifier]) 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]) { 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 *)collectionViewLayout insetForSectionAtIndex:(NSInteger)__unused section { CGFloat edgeInset = TGAttachmentEdgeInset; CGFloat leftInset = [self hasCameraInCurrentMode] ? 2 * edgeInset + 84.0f : edgeInset; CGFloat height = self.frame.size.height; if (collectionViewLayout == _smallLayout) height = [self _preferredHeightForZoomedIn:false progress:1.0f screenHeight:self.screenHeight]; else if (collectionViewLayout == _largeLayout) height = [self _preferredHeightForZoomedIn:true progress:1.0f screenHeight:self.screenHeight]; CGFloat cellHeight = height - 2 * edgeInset; CGFloat topInset = _collectionView.frame.size.height - cellHeight - edgeInset; CGFloat bottomInset = edgeInset; return UIEdgeInsetsMake(topInset, 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) + TGAttachmentEdgeInset * 2; frame.origin.y = self.frame.size.height - frame.size.height; if (!CGRectEqualToRect(frame, _collectionView.frame)) { bool invalidate = fabs(_collectionView.frame.size.height - frame.size.height) > FLT_EPSILON; _collectionView.frame = frame; if (invalidate) { [_smallLayout invalidateLayout]; [_largeLayout invalidateLayout]; [_collectionView layoutSubviews]; } } CGFloat height = self.frame.size.height; CGFloat cellHeight = height - 2 * TGAttachmentEdgeInset; CGFloat topInset = _collectionView.frame.size.height - cellHeight - TGAttachmentEdgeInset; frame = _cameraView.frame; frame.origin.y = topInset; _cameraView.frame = frame; [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]); } #pragma mark - - (UIView *)previewSourceView { return _collectionView; } - (UIViewController *)previewingContext:(id)previewingContext viewControllerForLocation:(CGPoint)location { if (self.openEditor) { return nil; } NSIndexPath *indexPath = [_collectionView indexPathForItemAtPoint:location]; if (indexPath == nil) return nil; CGRect cellFrame = [_collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath].frame; previewingContext.sourceRect = cellFrame; TGMediaAsset *asset = nil; _previewGalleryMixin = [self galleryMixinForIndexPath:indexPath previewMode:true outAsset:&asset]; UIViewController *controller = [_previewGalleryMixin galleryController]; CGSize screenSize = TGScreenSize(); controller.preferredContentSize = TGFitSize(asset.dimensions, screenSize); [_previewGalleryMixin setPreviewMode]; return controller; } - (void)previewingContext:(id)__unused previewingContext commitViewController:(UIViewController *)__unused viewControllerToCommit { _galleryMixin = _previewGalleryMixin; _previewGalleryMixin = nil; [self _setupGalleryMixin:_galleryMixin]; [_galleryMixin present]; } @end