mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1933 lines
87 KiB
Objective-C
1933 lines
87 KiB
Objective-C
#import "TGMediaAssetsPickerController.h"
|
|
|
|
#import <Photos/Photos.h>
|
|
#import <PhotosUI/PhotosUI.h>
|
|
#import "LegacyComponentsInternal.h"
|
|
|
|
#import "TGMediaGroupsController.h"
|
|
|
|
#import <LegacyComponents/TGMediaAssetMomentList.h>
|
|
#import <LegacyComponents/TGMenuView.h>
|
|
#import <LegacyComponents/TGTooltipView.h>
|
|
|
|
#import <LegacyComponents/TGFileUtils.h>
|
|
#import <LegacyComponents/TGPhotoEditorUtils.h>
|
|
#import <LegacyComponents/TGPaintUtils.h>
|
|
#import <LegacyComponents/UIImage+TG.h>
|
|
#import <LegacyComponents/TGGifConverter.h>
|
|
#import <CommonCrypto/CommonDigest.h>
|
|
|
|
#import "TGModernBarButton.h"
|
|
#import <LegacyComponents/TGMediaPickerToolbarView.h>
|
|
|
|
#import <LegacyComponents/TGMediaAsset+TGMediaEditableItem.h>
|
|
#import <LegacyComponents/TGMediaAssetImageSignals.h>
|
|
|
|
#import <LegacyComponents/TGPhotoEditorController.h>
|
|
|
|
#import <LegacyComponents/TGVideoEditAdjustments.h>
|
|
#import <LegacyComponents/TGPaintingData.h>
|
|
|
|
#import "TGModernButton.h"
|
|
#import "PGPhotoEditor.h"
|
|
|
|
@interface TGMediaPickerAccessView: UIView
|
|
{
|
|
TGMediaAssetsPallete *_pallete;
|
|
|
|
UIView *_backgroundView;
|
|
UIView *_separatorView;
|
|
UIView *_bottomSeparatorView;
|
|
UIImageView *_iconView;
|
|
UILabel *_labelView;
|
|
UILabel *_titleView;
|
|
UILabel *_textView;
|
|
TGModernButton * _buttonView;
|
|
|
|
CGSize _titleSize;
|
|
CGSize _textSize;
|
|
}
|
|
|
|
@property (nonatomic, assign) UIEdgeInsets safeAreaInset;
|
|
|
|
@property (nonatomic, copy) void (^pressed)(void);
|
|
|
|
@end
|
|
|
|
@implementation TGMediaPickerAccessView
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame {
|
|
self = [super initWithFrame:frame];
|
|
if (self != nil) {
|
|
_backgroundView = [[UIView alloc] init];
|
|
_separatorView = [[UIView alloc] init];
|
|
_bottomSeparatorView = [[UIView alloc] init];
|
|
_iconView = [[UIImageView alloc] init];
|
|
_labelView = [[UILabel alloc] init];
|
|
_titleView = [[UILabel alloc] init];
|
|
_textView = [[UILabel alloc] init];
|
|
|
|
_labelView.font = TGSystemFontOfSize(14.0);
|
|
_labelView.text = @"!";
|
|
_labelView.textAlignment = NSTextAlignmentCenter;
|
|
|
|
_titleView.font = TGSemiboldSystemFontOfSize(17.0);
|
|
_titleView.text = TGLocalized(@"Media.LimitedAccessTitle");
|
|
_titleView.numberOfLines = 1;
|
|
|
|
_textView.font = TGSystemFontOfSize(14.0);
|
|
_textView.text = TGLocalized(@"Media.LimitedAccessText");
|
|
_textView.numberOfLines = 3;
|
|
|
|
_buttonView = [[TGModernButton alloc] init];
|
|
_buttonView.adjustsImageWhenHighlighted = false;
|
|
_buttonView.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
|
|
_buttonView.contentEdgeInsets = UIEdgeInsetsMake(0.0, 15.0, 0.0, 0.0);
|
|
_buttonView.titleLabel.font = TGSystemFontOfSize(17.0f);
|
|
_buttonView.highlightBackgroundColor = UIColorRGB(0xebebeb);
|
|
[_buttonView setTitle:TGLocalized(@"Media.LimitedAccessManage") forState:UIControlStateNormal];
|
|
[_buttonView addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
[self addSubview:_backgroundView];
|
|
[self addSubview:_separatorView];
|
|
[self addSubview:_bottomSeparatorView];
|
|
[self addSubview:_iconView];
|
|
[self addSubview:_labelView];
|
|
[self addSubview:_titleView];
|
|
[self addSubview:_textView];
|
|
[self addSubview:_buttonView];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)buttonPressed {
|
|
self.pressed();
|
|
}
|
|
|
|
- (void)setPallete:(TGMediaAssetsPallete *)pallete {
|
|
_pallete = pallete;
|
|
|
|
_backgroundView.backgroundColor = pallete.backgroundColor;
|
|
_separatorView.backgroundColor = pallete.separatorColor;
|
|
_bottomSeparatorView.backgroundColor = pallete.separatorColor;
|
|
_titleView.textColor = pallete.textColor;
|
|
_textView.textColor = pallete.textColor;
|
|
_iconView.image = TGCircleImage(20.0, pallete.destructiveColor);
|
|
_labelView.textColor = pallete.badgeTextColor;
|
|
_buttonView.highlightBackgroundColor = pallete.selectionColor;
|
|
|
|
[_buttonView setTitleColor:pallete.accentColor];
|
|
}
|
|
|
|
- (CGSize)sizeThatFits:(CGSize)size {
|
|
CGSize result = CGSizeMake(size.width, 0.0);
|
|
|
|
CGSize constrainedSize = CGSizeMake(size.width - 30.0, size.height);
|
|
CGSize titleSize = [_titleView sizeThatFits:constrainedSize];
|
|
CGSize textSize = [_textView sizeThatFits:constrainedSize];
|
|
|
|
result.height += titleSize.height;
|
|
result.height += textSize.height;
|
|
result.height += 45.0;
|
|
result.height += 39.0;
|
|
result.height = CGFloor(result.height);
|
|
|
|
_titleSize = titleSize;
|
|
_textSize = textSize;
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void)setSafeAreaInset:(UIEdgeInsets)safeAreaInset {
|
|
_safeAreaInset = safeAreaInset;
|
|
[self layoutSubviews];
|
|
}
|
|
|
|
- (void)layoutSubviews {
|
|
_backgroundView.frame = self.bounds;
|
|
|
|
_iconView.frame = CGRectMake(self.safeAreaInset.left + 15.0, 16.0, 20.0, 20.0);
|
|
_labelView.frame = _iconView.frame;
|
|
|
|
_buttonView.contentEdgeInsets = UIEdgeInsetsMake(0.0, self.safeAreaInset.left + 15.0, 0.0, 0.0);
|
|
_titleView.frame = CGRectMake(self.safeAreaInset.left + 42.0, 15.0, _titleSize.width, _titleSize.height);
|
|
_textView.frame = CGRectMake(self.safeAreaInset.left + 15.0, 46.0, _textSize.width, _textSize.height);
|
|
_separatorView.frame = CGRectMake(self.safeAreaInset.left + 15.0, self.bounds.size.height - 46.0, self.bounds.size.width, TGScreenPixel);
|
|
_buttonView.frame = CGRectMake(0.0, self.bounds.size.height - 46.0, self.bounds.size.width, 46.0);
|
|
_bottomSeparatorView.frame = CGRectMake(0.0, self.bounds.size.height - TGScreenPixel, self.bounds.size.width, TGScreenPixel);
|
|
}
|
|
|
|
@end
|
|
|
|
@interface TGMediaAssetsController () <UINavigationControllerDelegate, ASWatcher>
|
|
{
|
|
TGMediaAssetsControllerIntent _intent;
|
|
|
|
TGMediaPickerToolbarView *_toolbarView;
|
|
|
|
TGMediaPickerAccessView *_accessView;
|
|
|
|
SMetaDisposable *_groupingChangedDisposable;
|
|
SMetaDisposable *_selectionChangedDisposable;
|
|
SMetaDisposable *_timersChangedDisposable;
|
|
SMetaDisposable *_adjustmentsChangedDisposable;
|
|
|
|
TGViewController *_searchController;
|
|
UIView *_searchSnapshotView;
|
|
|
|
NSTimer *_tooltipTimer;
|
|
TGMenuContainerView *_tooltipContainerView;
|
|
TGTooltipContainerView *_groupingTooltipContainerView;
|
|
|
|
id<LegacyComponentsContext> _context;
|
|
bool _saveEditedPhotos;
|
|
|
|
SMetaDisposable *_tooltipDismissDisposable;
|
|
}
|
|
|
|
@property (nonatomic, strong) ASHandle *actionHandle;
|
|
@property (nonatomic, readonly) TGMediaAssetsLibrary *assetsLibrary;
|
|
|
|
@end
|
|
|
|
@implementation TGMediaAssetsController
|
|
|
|
+ (instancetype)controllerWithContext:(id<LegacyComponentsContext>)context assetGroup:(TGMediaAssetGroup *)assetGroup intent:(TGMediaAssetsControllerIntent)intent recipientName:(NSString *)recipientName saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping selectionLimit:(int)selectionLimit
|
|
{
|
|
return [self controllerWithContext:context assetGroup:assetGroup intent:intent recipientName:recipientName saveEditedPhotos:saveEditedPhotos allowGrouping:allowGrouping inhibitSelection:false selectionLimit:selectionLimit];
|
|
}
|
|
|
|
+ (instancetype)controllerWithContext:(id<LegacyComponentsContext>)context assetGroup:(TGMediaAssetGroup *)assetGroup intent:(TGMediaAssetsControllerIntent)intent recipientName:(NSString *)recipientName saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping inhibitSelection:(bool)inhibitSelection selectionLimit:(int)selectionLimit
|
|
{
|
|
if (intent != TGMediaAssetsControllerSendMediaIntent && intent != TGMediaAssetsControllerSendFileIntent)
|
|
allowGrouping = false;
|
|
|
|
TGMediaAssetsController *assetsController = [[TGMediaAssetsController alloc] initWithContext:context intent:intent saveEditedPhotos:saveEditedPhotos allowGrouping:allowGrouping selectionLimit:selectionLimit];
|
|
|
|
__weak TGMediaAssetsController *weakController = assetsController;
|
|
void (^catchToolbarView)(bool) = ^(bool enabled)
|
|
{
|
|
__strong TGMediaAssetsController *strongController = weakController;
|
|
if (strongController == nil)
|
|
return;
|
|
|
|
if (strongController->_toolbarView.superview == nil)
|
|
return;
|
|
|
|
UIView *toolbarView = strongController->_toolbarView;
|
|
if (enabled)
|
|
{
|
|
if (toolbarView.superview != strongController.view)
|
|
return;
|
|
|
|
[strongController.pickerController.view addSubview:toolbarView];
|
|
}
|
|
else
|
|
{
|
|
if (toolbarView.superview == strongController.view)
|
|
return;
|
|
|
|
[strongController.view addSubview:toolbarView];
|
|
}
|
|
};
|
|
|
|
TGMediaGroupsController *groupsController = [[TGMediaGroupsController alloc] initWithContext:context assetsLibrary:assetsController.assetsLibrary intent:intent];
|
|
groupsController.pallete = assetsController.pallete;
|
|
groupsController.openAssetGroup = ^(id group)
|
|
{
|
|
__strong TGMediaAssetsController *strongController = weakController;
|
|
if (strongController == nil)
|
|
return;
|
|
|
|
TGMediaAssetsPickerController *pickerController = nil;
|
|
|
|
if ([group isKindOfClass:[TGMediaAssetGroup class]])
|
|
{
|
|
pickerController = [[TGMediaAssetsPickerController alloc] initWithContext:strongController->_context assetsLibrary:strongController.assetsLibrary assetGroup:group intent:intent selectionContext:inhibitSelection ? nil : strongController->_selectionContext editingContext:strongController->_editingContext saveEditedPhotos:strongController->_saveEditedPhotos];
|
|
pickerController.pallete = strongController.pallete;
|
|
}
|
|
pickerController.stickersContext = strongController.stickersContext;
|
|
pickerController.localMediaCacheEnabled = strongController.localMediaCacheEnabled;
|
|
pickerController.captionsEnabled = strongController.captionsEnabled;
|
|
pickerController.allowCaptionEntities = strongController.allowCaptionEntities;
|
|
pickerController.inhibitDocumentCaptions = strongController.inhibitDocumentCaptions;
|
|
pickerController.inhibitMute = strongController.inhibitMute;
|
|
pickerController.liveVideoUploadEnabled = strongController.liveVideoUploadEnabled;
|
|
pickerController.catchToolbarView = catchToolbarView;
|
|
pickerController.recipientName = recipientName;
|
|
pickerController.hasTimer = strongController.hasTimer;
|
|
pickerController.onlyCrop = strongController.onlyCrop;
|
|
pickerController.hasSilentPosting = strongController.hasSilentPosting;
|
|
pickerController.hasSchedule = strongController.hasSchedule;
|
|
pickerController.reminder = strongController.reminder;
|
|
pickerController.forum = strongController.forum;
|
|
pickerController.isSuggesting = strongController.isSuggesting;
|
|
pickerController.presentScheduleController = strongController.presentScheduleController;
|
|
pickerController.presentTimerController = strongController.presentTimerController;
|
|
[strongController pushViewController:pickerController animated:true];
|
|
};
|
|
[groupsController loadViewIfNeeded];
|
|
|
|
TGMediaAssetsPickerController *pickerController = [[TGMediaAssetsPickerController alloc] initWithContext:context assetsLibrary:assetsController.assetsLibrary assetGroup:assetGroup intent:intent selectionContext:inhibitSelection ? nil : assetsController->_selectionContext editingContext:assetsController->_editingContext saveEditedPhotos:saveEditedPhotos];
|
|
pickerController.pallete = assetsController.pallete;
|
|
pickerController.catchToolbarView = catchToolbarView;
|
|
|
|
[groupsController setIsFirstInStack:true];
|
|
[pickerController setIsFirstInStack:false];
|
|
|
|
if (intent == TGMediaAssetsControllerSendMediaIntent) {
|
|
[assetsController setViewControllers:@[ pickerController ]];
|
|
} else {
|
|
[assetsController setViewControllers:@[ groupsController, pickerController ]];
|
|
}
|
|
((TGNavigationBar *)assetsController.navigationBar).navigationController = assetsController;
|
|
|
|
assetsController.recipientName = recipientName;
|
|
|
|
return assetsController;
|
|
}
|
|
|
|
- (void)setStickersContext:(id<TGPhotoPaintStickersContext>)stickersContext
|
|
{
|
|
_stickersContext = stickersContext;
|
|
self.pickerController.stickersContext = stickersContext;
|
|
}
|
|
|
|
- (void)setCaptionsEnabled:(bool)captionsEnabled
|
|
{
|
|
_captionsEnabled = captionsEnabled;
|
|
self.pickerController.captionsEnabled = captionsEnabled;
|
|
}
|
|
|
|
- (void)setAllowCaptionEntities:(bool)allowCaptionEntities
|
|
{
|
|
_allowCaptionEntities = allowCaptionEntities;
|
|
self.pickerController.allowCaptionEntities = allowCaptionEntities;
|
|
}
|
|
|
|
- (void)setInhibitDocumentCaptions:(bool)inhibitDocumentCaptions
|
|
{
|
|
_inhibitDocumentCaptions = inhibitDocumentCaptions;
|
|
self.pickerController.inhibitDocumentCaptions = inhibitDocumentCaptions;
|
|
}
|
|
|
|
- (void)setInhibitMute:(bool)inhibitMute
|
|
{
|
|
_inhibitMute = inhibitMute;
|
|
self.pickerController.inhibitMute = inhibitMute;
|
|
}
|
|
|
|
- (void)setLiveVideoUploadEnabled:(bool)liveVideoUploadEnabled
|
|
{
|
|
_liveVideoUploadEnabled = liveVideoUploadEnabled;
|
|
self.pickerController.liveVideoUploadEnabled = liveVideoUploadEnabled;
|
|
}
|
|
|
|
- (void)setLocalMediaCacheEnabled:(bool)localMediaCacheEnabled
|
|
{
|
|
_localMediaCacheEnabled = localMediaCacheEnabled;
|
|
self.pickerController.localMediaCacheEnabled = localMediaCacheEnabled;
|
|
}
|
|
|
|
- (void)setShouldStoreAssets:(bool)shouldStoreAssets
|
|
{
|
|
_shouldStoreAssets = shouldStoreAssets;
|
|
self.pickerController.shouldStoreAssets = shouldStoreAssets;
|
|
}
|
|
|
|
- (void)setRecipientName:(NSString *)recipientName
|
|
{
|
|
_recipientName = recipientName;
|
|
self.pickerController.recipientName = recipientName;
|
|
}
|
|
|
|
- (void)setHasTimer:(bool)hasTimer
|
|
{
|
|
_hasTimer = hasTimer;
|
|
self.pickerController.hasTimer = hasTimer;
|
|
}
|
|
|
|
- (void)setHasSilentPosting:(bool)hasSilentPosting
|
|
{
|
|
_hasSilentPosting = hasSilentPosting;
|
|
self.pickerController.hasSilentPosting = hasSilentPosting;
|
|
}
|
|
|
|
- (void)setHasSchedule:(bool)hasSchedule
|
|
{
|
|
_hasSchedule = hasSchedule;
|
|
self.pickerController.hasSchedule = hasSchedule;
|
|
}
|
|
|
|
- (void)setReminder:(bool)reminder
|
|
{
|
|
_reminder = reminder;
|
|
self.pickerController.reminder = reminder;
|
|
}
|
|
|
|
- (void)setForum:(bool)forum {
|
|
_forum = forum;
|
|
self.pickerController.forum = forum;
|
|
}
|
|
|
|
- (void)setIsSuggesting:(bool)isSuggesting {
|
|
_isSuggesting = isSuggesting;
|
|
self.pickerController.isSuggesting = isSuggesting;
|
|
}
|
|
|
|
- (void)setPresentScheduleController:(void (^)(bool, void (^)(int32_t)))presentScheduleController {
|
|
_presentScheduleController = [presentScheduleController copy];
|
|
self.pickerController.presentScheduleController = presentScheduleController;
|
|
}
|
|
|
|
- (void)setPresentTimerController:(void (^)(void (^)(int32_t)))presentTimerController {
|
|
_presentTimerController = [presentTimerController copy];
|
|
self.pickerController.presentTimerController = presentTimerController;
|
|
}
|
|
|
|
- (void)setOnlyCrop:(bool)onlyCrop
|
|
{
|
|
_onlyCrop = onlyCrop;
|
|
self.pickerController.onlyCrop = onlyCrop;
|
|
}
|
|
|
|
- (TGMediaAssetsPickerController *)pickerController
|
|
{
|
|
TGMediaAssetsPickerController *pickerController = nil;
|
|
for (TGViewController *viewController in self.viewControllers)
|
|
{
|
|
if ([viewController isKindOfClass:[TGMediaAssetsPickerController class]])
|
|
{
|
|
pickerController = (TGMediaAssetsPickerController *)viewController;
|
|
break;
|
|
}
|
|
}
|
|
return pickerController;
|
|
}
|
|
|
|
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context intent:(TGMediaAssetsControllerIntent)intent saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping selectionLimit:(int)selectionLimit
|
|
{
|
|
self = [super initWithNavigationBarClass:[TGNavigationBar class] toolbarClass:[UIToolbar class]];
|
|
if (self != nil)
|
|
{
|
|
_context = context;
|
|
_saveEditedPhotos = saveEditedPhotos;
|
|
|
|
if ([context respondsToSelector:@selector(navigationBarPallete)])
|
|
[((TGNavigationBar *)self.navigationBar) setPallete:[context navigationBarPallete]];
|
|
|
|
if ([context respondsToSelector:@selector(mediaAssetsPallete)])
|
|
[self setPallete:[context mediaAssetsPallete]];
|
|
|
|
_actionHandle = [[ASHandle alloc] initWithDelegate:self releaseOnMainThread:true];
|
|
|
|
self.delegate = self;
|
|
_intent = intent;
|
|
_assetsLibrary = [TGMediaAssetsLibrary libraryForAssetType:[TGMediaAssetsController assetTypeForIntent:intent]];
|
|
|
|
__weak TGMediaAssetsController *weakSelf = self;
|
|
_selectionContext = [[TGMediaSelectionContext alloc] initWithGroupingAllowed:allowGrouping selectionLimit:selectionLimit];
|
|
if (allowGrouping)
|
|
_selectionContext.grouping = true;
|
|
_selectionContext.selectionLimitExceeded = ^{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
if (strongSelf->_selectionLimitExceeded) {
|
|
strongSelf->_selectionLimitExceeded();
|
|
}
|
|
};
|
|
[_selectionContext setItemSourceUpdatedSignal:[_assetsLibrary libraryChanged]];
|
|
_selectionContext.updatedItemsSignal = ^SSignal *(NSArray *items)
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return nil;
|
|
|
|
return [strongSelf->_assetsLibrary updatedAssetsForAssets:items];
|
|
};
|
|
|
|
bool (^updateGroupingButtonVisibility)(void) = ^bool
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return false;
|
|
|
|
bool onlyGroupableMedia = true;
|
|
for (TGMediaAsset *item in strongSelf->_selectionContext.selectedItems)
|
|
{
|
|
TGMediaAsset *asset = item;
|
|
if ([asset isKindOfClass:[TGCameraCapturedVideo class]]) {
|
|
asset = [(TGCameraCapturedVideo *)item originalAsset];
|
|
}
|
|
|
|
if (asset.type == TGMediaAssetGifType)
|
|
{
|
|
onlyGroupableMedia = false;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if ([[strongSelf->_editingContext timerForItem:asset] integerValue] > 0)
|
|
{
|
|
onlyGroupableMedia = false;
|
|
break;
|
|
}
|
|
|
|
id<TGMediaEditAdjustments> adjustments = [strongSelf->_editingContext adjustmentsForItem:asset];
|
|
if ([adjustments isKindOfClass:[TGMediaVideoEditAdjustments class]] && ((TGMediaVideoEditAdjustments *)adjustments).sendAsGif)
|
|
{
|
|
onlyGroupableMedia = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool groupingButtonVisible = strongSelf->_selectionContext.allowGrouping && onlyGroupableMedia && strongSelf->_selectionContext.count > 1;
|
|
groupingButtonVisible = false;
|
|
[strongSelf->_toolbarView setCenterButtonHidden:!groupingButtonVisible animated:true];
|
|
|
|
return groupingButtonVisible;
|
|
};
|
|
|
|
_selectionChangedDisposable = [[SMetaDisposable alloc] init];
|
|
[_selectionChangedDisposable setDisposable:[[_selectionContext selectionChangedSignal] startStrictWithNext:^(__unused id next)
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
bool groupingButtonVisible = updateGroupingButtonVisibility();
|
|
[strongSelf->_toolbarView setSelectedCount:strongSelf->_selectionContext.count animated:true];
|
|
[strongSelf->_toolbarView setRightButtonEnabled:strongSelf->_selectionContext.count > 0 animated:false];
|
|
|
|
if (groupingButtonVisible && [strongSelf shouldDisplayTooltip] && strongSelf->_selectionContext.grouping)
|
|
[strongSelf setupTooltip:[strongSelf->_toolbarView convertRect:strongSelf->_toolbarView.centerButton.frame toView:strongSelf.view]];
|
|
|
|
NSUInteger count = strongSelf->_selectionContext.count;
|
|
NSString *text = nil;
|
|
__block bool hasPhoto = false;
|
|
__block bool hasVideo = false;
|
|
[strongSelf->_selectionContext enumerateSelectedItems:^(id<TGMediaSelectableItem> asset) {
|
|
NSObject *value = (NSObject *)asset;
|
|
if (![value isKindOfClass:[TGMediaAsset class]])
|
|
return;
|
|
if (((TGMediaAsset *)asset).isVideo) {
|
|
hasVideo = true;
|
|
} else {
|
|
hasPhoto = true;
|
|
}
|
|
}];
|
|
|
|
if (hasPhoto && hasVideo) {
|
|
if (count == 1) {
|
|
text = @"1 media selected";
|
|
} else {
|
|
text = [NSString stringWithFormat:@"%lu medias selected", (unsigned long)count];
|
|
}
|
|
} else if (hasPhoto) {
|
|
if (count == 1) {
|
|
text = @"1 photo selected";
|
|
} else {
|
|
text = [NSString stringWithFormat:@"%lu photos selected", count];
|
|
}
|
|
} else if (hasVideo) {
|
|
if (count == 1) {
|
|
text = @"1 message selected";
|
|
} else {
|
|
text = [NSString stringWithFormat:@"%lu videos selected", count];
|
|
}
|
|
}
|
|
|
|
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, text);
|
|
} file:__FILE_NAME__ line:__LINE__]];
|
|
|
|
if (intent == TGMediaAssetsControllerSendMediaIntent || intent == TGMediaAssetsControllerSetProfilePhotoIntent || intent == TGMediaAssetsControllerSetSignupProfilePhotoIntent || intent == TGMediaAssetsControllerPassportIntent || intent == TGMediaAssetsControllerPassportMultipleIntent)
|
|
_editingContext = [[TGMediaEditingContext alloc] init];
|
|
else if (intent == TGMediaAssetsControllerSendFileIntent)
|
|
_editingContext = [TGMediaEditingContext contextForCaptionsOnly];
|
|
|
|
if (allowGrouping)
|
|
{
|
|
if (_groupingChangedDisposable) {
|
|
[_groupingChangedDisposable dispose];
|
|
}
|
|
_groupingChangedDisposable = [[SMetaDisposable alloc] init];
|
|
[_groupingChangedDisposable setDisposable:[_selectionContext.groupingChangedSignal startStrictWithNext:^(NSNumber *next)
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongSelf->_toolbarView setCenterButtonSelected:next.boolValue];
|
|
} file:__FILE_NAME__ line:__LINE__]];
|
|
|
|
if (_editingContext != nil)
|
|
{
|
|
if (_timersChangedDisposable) {
|
|
[_timersChangedDisposable dispose];
|
|
}
|
|
_timersChangedDisposable = [[SMetaDisposable alloc] init];
|
|
[_timersChangedDisposable setDisposable:[_editingContext.timersUpdatedSignal startStrictWithNext:^(__unused NSNumber *next)
|
|
{
|
|
updateGroupingButtonVisibility();
|
|
} file:__FILE_NAME__ line:__LINE__]];
|
|
|
|
if (_adjustmentsChangedDisposable) {
|
|
[_adjustmentsChangedDisposable dispose];
|
|
}
|
|
_adjustmentsChangedDisposable = [[SMetaDisposable alloc] init];
|
|
[_adjustmentsChangedDisposable setDisposable:[_editingContext.adjustmentsUpdatedSignal startStrictWithNext:^(__unused NSNumber *next)
|
|
{
|
|
updateGroupingButtonVisibility();
|
|
} file:__FILE_NAME__ line:__LINE__]];
|
|
}
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
self.delegate = nil;
|
|
[_selectionChangedDisposable dispose];
|
|
[_tooltipDismissDisposable dispose];
|
|
[_timersChangedDisposable dispose];
|
|
[_adjustmentsChangedDisposable dispose];
|
|
[_groupingChangedDisposable dispose];
|
|
}
|
|
|
|
- (void)loadView
|
|
{
|
|
[super loadView];
|
|
|
|
bool hasOnScreenNavigation = false;
|
|
if (iosMajorVersion() >= 11) {
|
|
if (@available(iOS 11.0, *)) {
|
|
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
|
|
}
|
|
}
|
|
|
|
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad && _intent == TGMediaAssetsControllerSendFileIntent) {
|
|
hasOnScreenNavigation = false;
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
CGFloat inset = [TGViewController safeAreaInsetForOrientation:self.interfaceOrientation hasOnScreenNavigation:hasOnScreenNavigation].bottom;
|
|
_toolbarView = [[TGMediaPickerToolbarView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - TGMediaPickerToolbarHeight - inset, self.view.frame.size.width, TGMediaPickerToolbarHeight + inset)];
|
|
if (_pallete != nil)
|
|
_toolbarView.pallete = _pallete;
|
|
_toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:self.interfaceOrientation hasOnScreenNavigation:hasOnScreenNavigation];
|
|
#pragma clang diagnostic pop
|
|
_toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
|
if ((_intent != TGMediaAssetsControllerSendFileIntent && _intent != TGMediaAssetsControllerSendMediaIntent && _intent != TGMediaAssetsControllerPassportMultipleIntent) || _selectionContext == nil)
|
|
[_toolbarView setRightButtonHidden:true];
|
|
|
|
__weak TGMediaAssetsController *weakSelf = self;
|
|
if (_selectionContext.allowGrouping)
|
|
{
|
|
[_toolbarView setCenterButtonImage:TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete != nil ? _pallete.secondaryTextColor : UIColorRGB(0x858e99))];
|
|
[_toolbarView setCenterButtonSelectedImage:_pallete != nil ? TGTintedImage(TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon"), _pallete.accentColor) : TGComponentsImageNamed(@"MediaPickerGroupPhotosIcon")];
|
|
[_toolbarView setCenterButtonHidden:true animated:false];
|
|
[_toolbarView setCenterButtonSelected:_selectionContext.grouping];
|
|
|
|
_toolbarView.centerPressed = ^
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
[strongSelf groupPhotosPressed];
|
|
};
|
|
}
|
|
if (_intent != TGMediaAssetsControllerSendMediaIntent)
|
|
[self.view addSubview:_toolbarView];
|
|
|
|
if (@available(iOS 14.0, *)) {
|
|
if ([PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite] == PHAuthorizationStatusLimited) {
|
|
_accessView = [[TGMediaPickerAccessView alloc] init];
|
|
_accessView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
_accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:self.interfaceOrientation hasOnScreenNavigation:hasOnScreenNavigation];
|
|
#pragma clang diagnostic pop
|
|
[_accessView setPallete:_pallete];
|
|
_accessView.pressed = ^{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf != nil) {
|
|
[strongSelf manageAccess];
|
|
}
|
|
};
|
|
[self.view addSubview:_accessView];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)manageAccess
|
|
{
|
|
if (iosMajorVersion() < 14) {
|
|
return;
|
|
}
|
|
|
|
__weak TGMediaAssetsController *weakSelf = self;
|
|
|
|
TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
|
|
controller.dismissesByOutsideTap = true;
|
|
controller.narrowInLandscape = true;
|
|
__weak TGMenuSheetController *weakController = controller;
|
|
|
|
NSArray *items = @
|
|
[
|
|
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessSelectMore") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
|
{
|
|
__strong TGMenuSheetController *strongController = weakController;
|
|
if (strongController == nil)
|
|
return;
|
|
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongController dismissAnimated:true manual:false completion:nil];
|
|
if (@available(iOS 14, *)) {
|
|
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:strongSelf];
|
|
}
|
|
}],
|
|
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Media.LimitedAccessChangeSettings") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
|
{
|
|
__strong TGMenuSheetController *strongController = weakController;
|
|
if (strongController == nil)
|
|
return;
|
|
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongController dismissAnimated:true manual:false completion:nil];
|
|
[[[LegacyComponentsGlobals provider] applicationInstance] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) {
|
|
|
|
}];
|
|
}],
|
|
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
|
|
{
|
|
__strong TGMenuSheetController *strongController = weakController;
|
|
if (strongController != nil)
|
|
[strongController dismissAnimated:true];
|
|
}]
|
|
];
|
|
|
|
[controller setItemViews:items];
|
|
[controller presentInViewController:self sourceView:self.view animated:true];
|
|
}
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
__weak TGMediaAssetsController *weakSelf = self;
|
|
_toolbarView.leftPressed = ^
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongSelf dismiss];
|
|
};
|
|
|
|
_toolbarView.rightPressed = ^
|
|
{
|
|
__strong TGMediaAssetsController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
[strongSelf completeWithCurrentItem:nil silentPosting:false scheduleTime:0];
|
|
};
|
|
}
|
|
|
|
- (void)groupPhotosPressed
|
|
{
|
|
[_selectionContext toggleGrouping];
|
|
|
|
[self showGroupingTooltip:_selectionContext.grouping duration:2.5];
|
|
}
|
|
|
|
- (void)dismiss
|
|
{
|
|
if (self.dismissalBlock != nil)
|
|
self.dismissalBlock();
|
|
|
|
[_editingContext clearPaintingData];
|
|
}
|
|
|
|
- (void)setPallete:(TGMediaAssetsPallete *)pallete {
|
|
_pallete = pallete;
|
|
|
|
[_accessView setPallete:pallete];
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews
|
|
{
|
|
[super viewDidLayoutSubviews];
|
|
|
|
UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
|
|
if (self.view.frame.size.width > self.view.frame.size.height)
|
|
orientation = UIInterfaceOrientationLandscapeLeft;
|
|
|
|
bool hasOnScreenNavigation = false;
|
|
if (iosMajorVersion() >= 11) {
|
|
if (@available(iOS 11.0, *)) {
|
|
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
|
|
}
|
|
}
|
|
|
|
_toolbarView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
|
_accessView.safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
|
|
|
|
if (_accessView != nil) {
|
|
CGSize accessSize = [_accessView sizeThatFits:self.view.frame.size];
|
|
_accessView.frame = CGRectMake(0.0, self.navigationBar.frame.size.height, self.view.frame.size.width, accessSize.height);
|
|
|
|
for (UIViewController *controller in self.viewControllers) {
|
|
if ([controller isKindOfClass:[TGMediaGroupsController class]]) {
|
|
((TGMediaGroupsController *)controller).topInset = accessSize.height;
|
|
} else if ([controller isKindOfClass:[TGMediaPickerController class]]) {
|
|
((TGMediaPickerController *)controller).topInset = accessSize.height;
|
|
}
|
|
}
|
|
}
|
|
if (_searchController == nil)
|
|
return;
|
|
|
|
CGSize screenSize = TGScreenSize();
|
|
UIView *view = _searchController.view;
|
|
|
|
CGRect frame = view.frame;
|
|
if (ABS(frame.size.width - screenSize.width) < FLT_EPSILON)
|
|
{
|
|
if (ABS(frame.size.height - screenSize.height + 20) < FLT_EPSILON)
|
|
{
|
|
frame.origin.y = frame.size.height - screenSize.height;
|
|
frame.size.height = screenSize.height;
|
|
}
|
|
else if (frame.size.height > screenSize.height + FLT_EPSILON)
|
|
{
|
|
frame.origin.y = 0;
|
|
frame.size.height = screenSize.height;
|
|
}
|
|
}
|
|
else if (ABS(frame.size.width - screenSize.height) < FLT_EPSILON)
|
|
{
|
|
if (frame.size.height > screenSize.width + FLT_EPSILON)
|
|
{
|
|
frame.origin.y = 0;
|
|
frame.size.height = screenSize.width;
|
|
}
|
|
}
|
|
|
|
if (ABS(frame.size.height) < FLT_EPSILON)
|
|
{
|
|
frame.size.height = screenSize.height;
|
|
}
|
|
|
|
if (!CGRectEqualToRect(view.frame, frame))
|
|
view.frame = frame;
|
|
|
|
[_searchController.view.superview bringSubviewToFront:_searchController.view];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)completeWithAvatarImage:(UIImage *)image commit:(void(^)(void))commit
|
|
{
|
|
if (self.avatarCompletionBlock != nil)
|
|
self.avatarCompletionBlock(image, commit);
|
|
}
|
|
|
|
- (void)completeWithAvatarVideo:(AVAsset *)asset adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image commit:(void(^)(void))commit
|
|
{
|
|
if (self.avatarVideoCompletionBlock != nil)
|
|
self.avatarVideoCompletionBlock(image, asset, adjustments, commit);
|
|
}
|
|
|
|
- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime
|
|
{
|
|
if (self.completionBlock != nil)
|
|
{
|
|
NSArray *signals = [self resultSignalsWithCurrentItem:currentItem descriptionGenerator:self.descriptionGenerator];
|
|
self.completionBlock(signals, silentPosting, scheduleTime);
|
|
}
|
|
else if (self.singleCompletionBlock != nil)
|
|
{
|
|
self.singleCompletionBlock(currentItem, _editingContext);
|
|
}
|
|
}
|
|
|
|
- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator
|
|
{
|
|
bool storeAssets = (_editingContext != nil) && self.shouldStoreAssets;
|
|
|
|
if (_intent == TGMediaAssetsControllerSendMediaIntent && _selectionContext.allowGrouping)
|
|
[[NSUserDefaults standardUserDefaults] setObject:@(!_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"];
|
|
|
|
return [TGMediaAssetsController resultSignalsForSelectionContext:_selectionContext editingContext:_editingContext intent:_intent currentItem:currentItem storeAssets:storeAssets convertToJpeg:false descriptionGenerator:descriptionGenerator saveEditedPhotos:_saveEditedPhotos];
|
|
}
|
|
|
|
+ (int64_t)generateGroupedId
|
|
{
|
|
int64_t value;
|
|
arc4random_buf(&value, sizeof(int64_t));
|
|
return value;
|
|
}
|
|
|
|
+ (NSArray *)resultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(TGMediaAsset *)currentItem storeAssets:(bool)storeAssets convertToJpeg:(bool)convertToJpeg descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator saveEditedPhotos:(bool)saveEditedPhotos
|
|
{
|
|
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
|
NSMutableArray *selectedItems = selectionContext.selectedItems ? [selectionContext.selectedItems mutableCopy] : [[NSMutableArray alloc] init];
|
|
if (selectedItems.count == 0 && currentItem != nil)
|
|
[selectedItems addObject:currentItem];
|
|
|
|
if (saveEditedPhotos && storeAssets && editingContext != nil)
|
|
{
|
|
NSMutableArray *fullSizeSignals = [[NSMutableArray alloc] init];
|
|
for (TGMediaAsset *asset in selectedItems)
|
|
{
|
|
if ([editingContext timerForItem:asset] == nil)
|
|
[fullSizeSignals addObject:[editingContext fullSizeImageUrlForItem:asset]];
|
|
}
|
|
|
|
SSignal *combinedSignal = nil;
|
|
SQueue *queue = [SQueue concurrentDefaultQueue];
|
|
|
|
for (SSignal *signal in fullSizeSignals)
|
|
{
|
|
if (combinedSignal == nil)
|
|
combinedSignal = [signal startOn:queue];
|
|
else
|
|
combinedSignal = [[combinedSignal then:signal] startOn:queue];
|
|
}
|
|
|
|
[[[[combinedSignal deliverOn:[SQueue mainQueue]] filter:^bool(id result)
|
|
{
|
|
return [result isKindOfClass:[NSURL class]];
|
|
}] mapToSignal:^SSignal *(NSURL *url)
|
|
{
|
|
return [[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImageAtUrl:url];
|
|
}] startWithNext:nil];
|
|
}
|
|
|
|
static dispatch_once_t onceToken;
|
|
static UIImage *blankImage;
|
|
dispatch_once(&onceToken, ^
|
|
{
|
|
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), true, 0.0f);
|
|
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
|
|
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
|
|
|
|
blankImage = UIGraphicsGetImageFromCurrentImageContext();
|
|
UIGraphicsEndImageContext();
|
|
});
|
|
|
|
CGSize fallbackThumbnailImageSize = CGSizeMake(256, 256);
|
|
SSignal *(^inlineThumbnailSignal)(TGMediaAsset *) = ^SSignal *(TGMediaAsset *asset)
|
|
{
|
|
return [[[TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeAspectRatioThumbnail size:fallbackThumbnailImageSize allowNetworkAccess:false] takeLast] catch:^SSignal *(id error)
|
|
{
|
|
if ([error respondsToSelector:@selector(boolValue)] && [error boolValue]) {
|
|
return [[TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeAspectRatioThumbnail size:fallbackThumbnailImageSize allowNetworkAccess:true] takeLast];
|
|
} else {
|
|
return [SSignal single:blankImage];
|
|
}
|
|
}];
|
|
};
|
|
|
|
NSNumber *groupedId;
|
|
NSInteger i = 0;
|
|
NSInteger num = 0;
|
|
bool grouping = selectionContext.grouping;
|
|
|
|
bool hasAnyTimers = false;
|
|
if (editingContext != nil || grouping)
|
|
{
|
|
for (TGMediaAsset *asset in selectedItems)
|
|
{
|
|
if ([editingContext timerForItem:asset] != nil) {
|
|
hasAnyTimers = true;
|
|
}
|
|
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
|
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) {
|
|
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
|
|
if (videoAdjustments.sendAsGif) {
|
|
grouping = false;
|
|
}
|
|
}
|
|
if (adjustments.paintingData.hasAnimation) {
|
|
grouping = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (grouping && selectedItems.count > 1)
|
|
groupedId = @([self generateGroupedId]);
|
|
|
|
for (TGMediaAsset *item in selectedItems)
|
|
{
|
|
TGMediaAsset *asset = item;
|
|
if ([asset isKindOfClass:[TGCameraCapturedVideo class]]) {
|
|
asset = ((TGCameraCapturedVideo *)asset).originalAsset;
|
|
}
|
|
|
|
NSAttributedString *caption = [editingContext captionForItem:asset];
|
|
|
|
if (editingContext.isForcedCaption) {
|
|
if (grouping && num > 0) {
|
|
caption = nil;
|
|
} else if (!grouping && num < selectedItems.count - 1) {
|
|
caption = nil;
|
|
}
|
|
}
|
|
|
|
bool spoiler = [editingContext spoilerForItem:item];
|
|
|
|
switch (asset.type)
|
|
{
|
|
case TGMediaAssetPhotoType:
|
|
{
|
|
if (intent == TGMediaAssetsControllerSendFileIntent)
|
|
{
|
|
[signals addObject:[[[TGMediaAssetImageSignals imageDataForAsset:asset allowNetworkAccess:false convertToJpeg:convertToJpeg] map:^NSDictionary *(TGMediaAssetImageData *assetData)
|
|
{
|
|
NSString *tempFileName = TGTemporaryFileName(nil);
|
|
[assetData.imageData writeToURL:[NSURL fileURLWithPath:tempFileName] atomically:true];
|
|
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"file";
|
|
dict[@"tempFileUrl"] = [NSURL fileURLWithPath:tempFileName];
|
|
dict[@"fileName"] = assetData.fileName;
|
|
dict[@"mimeType"] = TGMimeTypeForFileUTI(assetData.fileUTI);
|
|
|
|
if (groupedId != nil)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}] catch:^SSignal *(id error)
|
|
{
|
|
if (![error isKindOfClass:[NSNumber class]])
|
|
return [SSignal complete];
|
|
|
|
return [inlineThumbnailSignal(asset) map:^id(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"cloudPhoto";
|
|
dict[@"document"] = @true;
|
|
dict[@"asset"] = asset;
|
|
dict[@"previewImage"] = image;
|
|
dict[@"mimeType"] = TGMimeTypeForFileUTI(asset.uniformTypeIdentifier);
|
|
|
|
NSString *fileName = asset.fileName;
|
|
NSRange range = [fileName.lowercaseString rangeOfString:@".heic"];
|
|
if (range.location != NSNotFound)
|
|
fileName = [fileName stringByReplacingCharactersInRange:range withString:@".JPG"];
|
|
|
|
dict[@"fileName"] = fileName;
|
|
|
|
if (groupedId != nil)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}];
|
|
}]];
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
else
|
|
{
|
|
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
|
NSNumber *timer = [editingContext timerForItem:asset];
|
|
|
|
SSignal *inlineSignal = [inlineThumbnailSignal(asset) map:^id(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"cloudPhoto";
|
|
dict[@"document"] = @false;
|
|
dict[@"asset"] = asset;
|
|
dict[@"previewImage"] = image;
|
|
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
else if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}];
|
|
|
|
SSignal *assetSignal = inlineSignal;
|
|
SSignal *imageSignal = assetSignal;
|
|
if (adjustments.sendAsGif)
|
|
{
|
|
NSTimeInterval trimStartValue = 0.0;
|
|
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) {
|
|
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
|
|
trimStartValue = videoAdjustments.trimStartValue;
|
|
}
|
|
|
|
UIImage *(^cropVideoThumbnail)(UIImage *, CGSize, CGSize, bool) = ^UIImage *(UIImage *image, CGSize targetSize, CGSize sourceSize, bool resize)
|
|
{
|
|
if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
|
|
{
|
|
CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height);
|
|
UIImage *paintingImage = adjustments.paintingData.stillImage;
|
|
if (paintingImage == nil) {
|
|
paintingImage = adjustments.paintingData.image;
|
|
}
|
|
if (adjustments.toolsApplied) {
|
|
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
|
|
}
|
|
return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize);
|
|
}
|
|
|
|
return image;
|
|
};
|
|
|
|
SSignal *trimmedVideoThumbnailSignal = [[TGMediaAssetImageSignals avAssetForVideoAsset:asset allowNetworkAccess:false] mapToSignal:^SSignal *(AVAsset *avAsset)
|
|
{
|
|
CGSize imageSize = TGFillSize(asset.dimensions, CGSizeMake(512, 512));
|
|
return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, TGScaleToFill(asset.dimensions, CGSizeMake(512, 512)), asset.dimensions, true);
|
|
}];
|
|
}];
|
|
|
|
SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, image.size, image.size, false);
|
|
}];
|
|
|
|
SSignal *thumbnailSignal = trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
|
|
|
|
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
|
|
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
|
|
|
|
TGCameraCapturedVideo *videoAsset = [[TGCameraCapturedVideo alloc] initWithAsset:asset livePhoto:true];
|
|
[signals addObject:[thumbnailSignal mapToSignal:^SSignal *(UIImage *image)
|
|
{
|
|
return [videoAsset.avAsset map:^id(AVURLAsset *avAsset) {
|
|
NSTimeInterval duration = CMTimeGetSeconds(avAsset.duration);
|
|
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) {
|
|
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
|
|
duration = videoAdjustments.trimApplied ? (videoAdjustments.trimEndValue - videoAdjustments.trimStartValue) : duration;
|
|
}
|
|
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"cameraVideo";
|
|
dict[@"url"] = avAsset.URL;
|
|
dict[@"previewImage"] = image;
|
|
dict[@"duration"] = @(duration);
|
|
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
|
dict[@"adjustments"] = adjustments;
|
|
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
else if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}];
|
|
}]];
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
else
|
|
{
|
|
if (editingContext != nil)
|
|
{
|
|
imageSignal = [[[[[editingContext imageSignalForItem:asset withUpdates:true] filter:^bool(id result)
|
|
{
|
|
return result == nil || ([result isKindOfClass:[UIImage class]] && !((UIImage *)result).degraded);
|
|
}] take:1] mapToSignal:^SSignal *(id result)
|
|
{
|
|
if (result == nil)
|
|
{
|
|
return [SSignal fail:nil];
|
|
}
|
|
else if ([result isKindOfClass:[UIImage class]])
|
|
{
|
|
UIImage *image = (UIImage *)result;
|
|
image.edited = true;
|
|
return [SSignal single:image];
|
|
}
|
|
|
|
return [SSignal complete];
|
|
}] onCompletion:^
|
|
{
|
|
__strong TGMediaEditingContext *strongEditingContext = editingContext;
|
|
[strongEditingContext description];
|
|
}];
|
|
}
|
|
|
|
[signals addObject:[[imageSignal map:^NSDictionary *(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"editedPhoto";
|
|
dict[@"image"] = image;
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
|
|
bool animated = adjustments.paintingData.hasAnimation;
|
|
if (animated) {
|
|
dict[@"isAnimation"] = @true;
|
|
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) {
|
|
dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)adjustments preset:TGMediaVideoConversionPresetAnimation];
|
|
} else {
|
|
dict[@"adjustments"] = adjustments;
|
|
}
|
|
|
|
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"gifvideo_%x.jpg", (int)arc4random()]];
|
|
NSData *data = UIImageJPEGRepresentation(image, 0.8);
|
|
[data writeToFile:filePath atomically:true];
|
|
dict[@"url"] = [NSURL fileURLWithPath:filePath];
|
|
|
|
if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
|
|
{
|
|
UIImage *paintingImage = adjustments.paintingData.stillImage;
|
|
if (paintingImage == nil) {
|
|
paintingImage = adjustments.paintingData.image;
|
|
}
|
|
UIImage *thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(512, 512)), adjustments.originalSize, true, true, true, false);
|
|
if (thumbnailImage != nil) {
|
|
dict[@"previewImage"] = thumbnailImage;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
else if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}] catch:^SSignal *(__unused id error)
|
|
{
|
|
return inlineSignal;
|
|
}]];
|
|
}
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TGMediaAssetVideoType:
|
|
{
|
|
if (intent == TGMediaAssetsControllerSendFileIntent)
|
|
{
|
|
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
|
|
|
CGSize dimensions = asset.originalSize;
|
|
NSTimeInterval duration = asset.videoDuration;
|
|
|
|
[signals addObject:[inlineThumbnailSignal(asset) map:^id(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"video";
|
|
dict[@"document"] = @true;
|
|
dict[@"asset"] = asset;
|
|
dict[@"previewImage"] = image;
|
|
dict[@"fileName"] = asset.fileName;
|
|
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
|
dict[@"duration"] = @(duration);
|
|
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
|
|
if (groupedId != nil)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}]];
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
else
|
|
{
|
|
TGVideoEditAdjustments *adjustments = (TGVideoEditAdjustments *)[editingContext adjustmentsForItem:asset];
|
|
NSNumber *timer = [editingContext timerForItem:asset];
|
|
|
|
UIImage *(^cropVideoThumbnail)(UIImage *, CGSize, CGSize, bool) = ^UIImage *(UIImage *image, CGSize targetSize, CGSize sourceSize, bool resize)
|
|
{
|
|
if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
|
|
{
|
|
CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height);
|
|
UIImage *paintingImage = adjustments.paintingData.stillImage;
|
|
if (paintingImage == nil) {
|
|
paintingImage = adjustments.paintingData.image;
|
|
}
|
|
if (adjustments.toolsApplied) {
|
|
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
|
|
}
|
|
return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize);
|
|
}
|
|
|
|
return image;
|
|
};
|
|
|
|
SSignal *trimmedVideoThumbnailSignal = [[TGMediaAssetImageSignals avAssetForVideoAsset:asset allowNetworkAccess:false] mapToSignal:^SSignal *(AVAsset *avAsset)
|
|
{
|
|
CGSize imageSize = TGFillSize(asset.dimensions, CGSizeMake(512, 512));
|
|
return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, TGScaleToFill(asset.dimensions, CGSizeMake(512, 512)), asset.dimensions, true);
|
|
}];
|
|
}];
|
|
|
|
SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, image.size, image.size, false);
|
|
}];
|
|
|
|
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
|
|
|
|
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
|
|
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
|
|
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : asset.videoDuration;
|
|
|
|
[signals addObject:[thumbnailSignal map:^id(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"video";
|
|
dict[@"document"] = @false;
|
|
dict[@"asset"] = asset;
|
|
dict[@"previewImage"] = image;
|
|
dict[@"adjustments"] = adjustments;
|
|
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
|
dict[@"duration"] = @(duration);
|
|
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
else if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}]];
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TGMediaAssetGifType:
|
|
{
|
|
TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)item;
|
|
if ([video isKindOfClass:[TGMediaAsset class]]) {
|
|
video = [[TGCameraCapturedVideo alloc] initWithAsset:(TGMediaAsset *)video livePhoto:false];
|
|
}
|
|
|
|
TGVideoEditAdjustments *adjustments = (TGVideoEditAdjustments *)[editingContext adjustmentsForItem:video];
|
|
NSNumber *timer = [editingContext timerForItem:video];
|
|
|
|
UIImage *(^cropVideoThumbnail)(UIImage *, CGSize, CGSize, bool) = ^UIImage *(UIImage *image, CGSize targetSize, CGSize sourceSize, bool resize)
|
|
{
|
|
if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
|
|
{
|
|
CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height);
|
|
UIImage *paintingImage = adjustments.paintingData.stillImage;
|
|
if (paintingImage == nil) {
|
|
paintingImage = adjustments.paintingData.image;
|
|
}
|
|
if (adjustments.toolsApplied) {
|
|
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
|
|
}
|
|
return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize);
|
|
}
|
|
|
|
return image;
|
|
};
|
|
|
|
CGSize imageSize = TGFillSize(video.originalSize, CGSizeMake(512, 512));
|
|
SSignal *trimmedVideoThumbnailSignal = [[video avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
|
|
return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, TGScaleToFill(video.originalSize, CGSizeMake(512, 512)), video.originalSize, true);
|
|
}];
|
|
}];
|
|
|
|
SSignal *videoThumbnailSignal = [[video thumbnailImageSignal] map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, image.size, image.size, false);
|
|
}];
|
|
|
|
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
|
|
|
|
TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation;
|
|
if (adjustments != nil) {
|
|
adjustments = [adjustments editAdjustmentsWithPreset:preset maxDuration:0.0];
|
|
} else {
|
|
adjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:video.originalSize preset:preset];
|
|
}
|
|
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:video.originalSize adjustments:adjustments preset:preset];
|
|
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : video.videoDuration;
|
|
|
|
[signals addObject:[thumbnailSignal map:^id(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"cameraVideo";
|
|
dict[@"url"] = video.immediateAVAsset.URL;
|
|
dict[@"previewImage"] = image;
|
|
dict[@"adjustments"] = adjustments;
|
|
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
|
dict[@"duration"] = @(duration);
|
|
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, asset.identifier);
|
|
return generatedItem;
|
|
}]];
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (groupedId != nil && i == 10) {
|
|
i = 0;
|
|
groupedId = @([self generateGroupedId]);
|
|
}
|
|
}
|
|
return signals;
|
|
}
|
|
|
|
+ (NSArray *)pasteboardResultSignalsForSelectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext intent:(TGMediaAssetsControllerIntent)intent currentItem:(id<TGMediaSelectableItem>)currentItem descriptionGenerator:(id (^)(id, NSAttributedString *, NSString *, NSString *))descriptionGenerator
|
|
{
|
|
NSMutableArray *signals = [[NSMutableArray alloc] init];
|
|
NSMutableArray *selectedItems = [selectionContext.selectedItems mutableCopy];
|
|
if (selectedItems.count == 0 && currentItem != nil)
|
|
[selectedItems addObject:currentItem];
|
|
|
|
NSNumber *groupedId;
|
|
NSInteger i = 0;
|
|
NSInteger num = 0;
|
|
bool grouping = selectionContext.grouping;
|
|
|
|
bool hasAnyTimers = false;
|
|
if (editingContext != nil || grouping)
|
|
{
|
|
for (id<TGMediaEditableItem> asset in selectedItems)
|
|
{
|
|
if ([editingContext timerForItem:asset] != nil) {
|
|
hasAnyTimers = true;
|
|
}
|
|
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
|
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]]) {
|
|
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
|
|
if (videoAdjustments.sendAsGif) {
|
|
grouping = false;
|
|
}
|
|
}
|
|
if (adjustments.paintingData.hasAnimation) {
|
|
grouping = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (grouping && selectedItems.count > 1)
|
|
groupedId = @([self generateGroupedId]);
|
|
|
|
for (id<TGMediaEditableItem> asset in selectedItems)
|
|
{
|
|
NSAttributedString *caption = [editingContext captionForItem:asset];
|
|
if (editingContext.isForcedCaption) {
|
|
if (grouping && num > 0) {
|
|
caption = nil;
|
|
} else if (!grouping && num < selectedItems.count - 1) {
|
|
caption = nil;
|
|
}
|
|
}
|
|
|
|
bool spoiler = [editingContext spoilerForItem:asset];
|
|
|
|
if ([asset isKindOfClass:[UIImage class]]) {
|
|
if (intent == TGMediaAssetsControllerSendFileIntent)
|
|
{
|
|
NSString *tempFileName = TGTemporaryFileName(nil);
|
|
NSData *imageData = UIImageJPEGRepresentation((UIImage *)asset, 1.0);
|
|
[imageData writeToURL:[NSURL fileURLWithPath:tempFileName] atomically:true];
|
|
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"file";
|
|
dict[@"tempFileUrl"] = [NSURL fileURLWithPath:tempFileName];
|
|
dict[@"fileName"] = [NSString stringWithFormat:@"IMG%03ld.jpg", i];
|
|
dict[@"mimeType"] = TGMimeTypeForFileUTI(@"image/jpeg");
|
|
dict[@"previewImage"] = asset;
|
|
|
|
if (groupedId != nil)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
|
|
[signals addObject:[SSignal single:generatedItem]];
|
|
|
|
i++;
|
|
num++;
|
|
} else {
|
|
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
|
NSNumber *timer = [editingContext timerForItem:asset];
|
|
|
|
SSignal *inlineSignal = [[SSignal single:asset] map:^id(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"editedPhoto";
|
|
dict[@"image"] = image;
|
|
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
|
|
if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
|
|
return generatedItem;
|
|
}];
|
|
|
|
SSignal *assetSignal = inlineSignal;
|
|
SSignal *imageSignal = assetSignal;
|
|
if (editingContext != nil)
|
|
{
|
|
imageSignal = [[[[[editingContext imageSignalForItem:asset withUpdates:true] filter:^bool(id result)
|
|
{
|
|
return result == nil || ([result isKindOfClass:[UIImage class]] && !((UIImage *)result).degraded);
|
|
}] take:1] mapToSignal:^SSignal *(id result)
|
|
{
|
|
if (result == nil)
|
|
{
|
|
return [SSignal fail:nil];
|
|
}
|
|
else if ([result isKindOfClass:[UIImage class]])
|
|
{
|
|
UIImage *image = (UIImage *)result;
|
|
image.edited = true;
|
|
return [SSignal single:image];
|
|
}
|
|
|
|
return [SSignal complete];
|
|
}] onCompletion:^
|
|
{
|
|
__strong TGMediaEditingContext *strongEditingContext = editingContext;
|
|
[strongEditingContext description];
|
|
}];
|
|
}
|
|
|
|
[signals addObject:[[imageSignal map:^NSDictionary *(UIImage *image)
|
|
{
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"editedPhoto";
|
|
dict[@"image"] = image;
|
|
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
|
|
if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
|
|
return generatedItem;
|
|
}] catch:^SSignal *(__unused id error)
|
|
{
|
|
return inlineSignal;
|
|
}]];
|
|
|
|
i++;
|
|
num++;
|
|
}
|
|
} else if ([asset isKindOfClass:[TGCameraCapturedVideo class]]) {
|
|
TGCameraCapturedVideo *video = (TGCameraCapturedVideo *)asset;
|
|
|
|
TGVideoEditAdjustments *adjustments = (TGVideoEditAdjustments *)[editingContext adjustmentsForItem:asset];
|
|
NSAttributedString *caption = [editingContext captionForItem:asset];
|
|
NSNumber *timer = [editingContext timerForItem:asset];
|
|
|
|
UIImage *(^cropVideoThumbnail)(UIImage *, CGSize, CGSize, bool) = ^UIImage *(UIImage *image, CGSize targetSize, CGSize sourceSize, bool resize)
|
|
{
|
|
if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied)
|
|
{
|
|
CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height);
|
|
UIImage *paintingImage = adjustments.paintingData.stillImage;
|
|
if (paintingImage == nil) {
|
|
paintingImage = adjustments.paintingData.image;
|
|
}
|
|
if (adjustments.toolsApplied) {
|
|
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
|
|
}
|
|
return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize);
|
|
}
|
|
|
|
return image;
|
|
};
|
|
|
|
CGSize imageSize = TGFillSize(asset.originalSize, CGSizeMake(512, 512));
|
|
SSignal *trimmedVideoThumbnailSignal = [[video avAsset] mapToSignal:^SSignal *(AVURLAsset *avAsset) {
|
|
return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image)
|
|
{
|
|
return cropVideoThumbnail(image, TGScaleToFill(asset.originalSize, CGSizeMake(512, 512)), asset.originalSize, true);
|
|
}];
|
|
}];
|
|
|
|
SSignal *(^inlineThumbnailSignal)(id<TGMediaEditableItem>) = ^SSignal *(id<TGMediaEditableItem> item)
|
|
{
|
|
return [item thumbnailImageSignal];
|
|
};
|
|
|
|
SSignal *videoThumbnailSignal = [inlineThumbnailSignal(asset) map:^UIImage *(UIImage *image) {
|
|
return cropVideoThumbnail(image, image.size, image.size, false);
|
|
}];
|
|
|
|
SSignal *thumbnailSignal = adjustments.trimStartValue > FLT_EPSILON ? trimmedVideoThumbnailSignal : videoThumbnailSignal;
|
|
|
|
TGMediaVideoConversionPreset preset = [TGMediaVideoConverter presetFromAdjustments:adjustments];
|
|
CGSize dimensions = [TGMediaVideoConverter dimensionsFor:asset.originalSize adjustments:adjustments preset:preset];
|
|
NSTimeInterval duration = adjustments.trimApplied ? (adjustments.trimEndValue - adjustments.trimStartValue) : video.videoDuration;
|
|
|
|
[signals addObject:[thumbnailSignal mapToSignal:^id(UIImage *image)
|
|
{
|
|
return [video.avAsset map:^id(AVURLAsset *avAsset) {
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
dict[@"type"] = @"cameraVideo";
|
|
dict[@"url"] = avAsset.URL;
|
|
dict[@"previewImage"] = image;
|
|
dict[@"adjustments"] = adjustments;
|
|
dict[@"dimensions"] = [NSValue valueWithCGSize:dimensions];
|
|
dict[@"duration"] = @(duration);
|
|
|
|
if (adjustments.paintingData.stickers.count > 0)
|
|
dict[@"stickers"] = adjustments.paintingData.stickers;
|
|
if (timer != nil)
|
|
dict[@"timer"] = timer;
|
|
else if (groupedId != nil && !hasAnyTimers)
|
|
dict[@"groupedId"] = groupedId;
|
|
|
|
if (spoiler) {
|
|
dict[@"spoiler"] = @true;
|
|
}
|
|
|
|
id generatedItem = descriptionGenerator(dict, caption, nil, nil);
|
|
return generatedItem;
|
|
}];
|
|
}]];
|
|
|
|
i++;
|
|
i++;
|
|
num++;
|
|
}
|
|
|
|
if (groupedId != nil && i == 10)
|
|
{
|
|
i = 0;
|
|
groupedId = @([self generateGroupedId]);
|
|
}
|
|
}
|
|
return signals;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (UIBarButtonItem *)leftBarButtonItem
|
|
{
|
|
if (_intent == TGMediaAssetsControllerSendMediaIntent) {
|
|
return [[UIBarButtonItem alloc] initWithTitle:TGLocalized(@"Common.Cancel") style:UIBarButtonItemStylePlain target:self action:@selector(cancelButtonPressed)];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (UIBarButtonItem *)rightBarButtonItem
|
|
{
|
|
return nil;
|
|
// if (_intent == TGMediaAssetsControllerSendFileIntent)
|
|
// return nil;
|
|
// if (self.requestSearchController == nil) {
|
|
// return nil;
|
|
// }
|
|
//
|
|
// if (iosMajorVersion() < 7)
|
|
// {
|
|
// TGModernBarButton *searchButton = [[TGModernBarButton alloc] initWithImage:TGComponentsImageNamed(@"NavigationSearchIcon.png")];
|
|
// searchButton.portraitAdjustment = CGPointMake(-7, -5);
|
|
// [searchButton addTarget:self action:@selector(searchButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
|
// return [[UIBarButtonItem alloc] initWithCustomView:searchButton];
|
|
// }
|
|
//
|
|
// return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(searchButtonPressed)];
|
|
}
|
|
|
|
- (void)cancelButtonPressed
|
|
{
|
|
[self dismiss];
|
|
}
|
|
|
|
- (void)searchButtonPressed
|
|
{
|
|
if (self.requestSearchController) {
|
|
self.requestSearchController();
|
|
}
|
|
}
|
|
|
|
- (void)send:(bool)silently whenOnline:(bool)whenOnlne
|
|
{
|
|
[self completeWithCurrentItem:nil silentPosting:silently scheduleTime:whenOnlne ? 0x7ffffffe : 0];
|
|
}
|
|
|
|
- (void)schedule:(bool)media {
|
|
__weak TGMediaAssetsController *weakSelf = self;
|
|
self.presentScheduleController(media, ^(int32_t scheduleTime) {
|
|
[weakSelf completeWithCurrentItem:nil silentPosting:false scheduleTime:scheduleTime];
|
|
});
|
|
}
|
|
|
|
- (void)navigationController:(UINavigationController *)__unused navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)__unused animated
|
|
{
|
|
if (_searchController == nil)
|
|
return;
|
|
|
|
UIView *backArrow = nil;
|
|
UIView *backButton = nil;
|
|
|
|
if ([viewController isKindOfClass:[TGPhotoEditorController class]])
|
|
{
|
|
backArrow.alpha = 0.0f;
|
|
backButton.alpha = 0.0f;
|
|
|
|
_searchSnapshotView = [_searchController.view snapshotViewAfterScreenUpdates:false];
|
|
_searchSnapshotView.frame = CGRectOffset([_searchController.view convertRect:_searchController.view.frame toView:self.navigationBar], -_searchSnapshotView.frame.size.width, 0);
|
|
[self.navigationBar addSubview:_searchSnapshotView];
|
|
_searchController.view.hidden = true;
|
|
}
|
|
else if ([viewController isKindOfClass:[TGMediaAssetsPickerController class]])
|
|
{
|
|
[_searchSnapshotView.superview bringSubviewToFront:_searchSnapshotView];
|
|
|
|
backArrow.alpha = 0.0f;
|
|
backButton.alpha = 0.0f;
|
|
}
|
|
}
|
|
|
|
- (void)navigationController:(UINavigationController *)__unused navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)__unused animated
|
|
{
|
|
if (_searchController == nil)
|
|
return;
|
|
|
|
if ([viewController isKindOfClass:[TGMediaAssetsPickerController class]])
|
|
{
|
|
[_searchSnapshotView removeFromSuperview];
|
|
_searchSnapshotView = nil;
|
|
_searchController.view.hidden = false;
|
|
|
|
UIView *backArrow = nil;
|
|
UIView *backButton = nil;
|
|
backArrow.alpha = 1.0f;
|
|
backButton.alpha = 1.0f;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
+ (TGMediaAssetType)assetTypeForIntent:(TGMediaAssetsControllerIntent)intent
|
|
{
|
|
TGMediaAssetType assetType = TGMediaAssetAnyType;
|
|
|
|
switch (intent)
|
|
{
|
|
case TGMediaAssetsControllerSetSignupProfilePhotoIntent:
|
|
case TGMediaAssetsControllerSetCustomWallpaperIntent:
|
|
case TGMediaAssetsControllerPassportIntent:
|
|
case TGMediaAssetsControllerPassportMultipleIntent:
|
|
assetType = TGMediaAssetPhotoType;
|
|
break;
|
|
|
|
case TGMediaAssetsControllerSetProfilePhotoIntent:
|
|
case TGMediaAssetsControllerSendMediaIntent:
|
|
assetType = TGMediaAssetAnyType;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return assetType;
|
|
}
|
|
|
|
#pragma mark - Grouping Tooltip
|
|
|
|
- (bool)shouldDisplayTooltip
|
|
{
|
|
return ![[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedGroupTooltip_v0"] boolValue];
|
|
}
|
|
|
|
- (void)setupTooltip:(CGRect)rect
|
|
{
|
|
if (_tooltipContainerView != nil)
|
|
return;
|
|
|
|
rect = CGRectOffset(rect, 0.0f, 15.0f);
|
|
|
|
_tooltipTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(tooltipTimerTick) interval:3.0 repeat:false];
|
|
|
|
_tooltipContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height)];
|
|
[self.view addSubview:_tooltipContainerView];
|
|
|
|
NSMutableArray *actions = [[NSMutableArray alloc] init];
|
|
[actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"MediaPicker.TapToUngroupDescription"), @"title", nil]];
|
|
|
|
[_tooltipContainerView.menuView setButtonsAndActions:actions watcherHandle:_actionHandle];
|
|
[_tooltipContainerView.menuView sizeToFit];
|
|
_tooltipContainerView.menuView.buttonHighlightDisabled = true;
|
|
|
|
[_tooltipContainerView showMenuFromRect:rect animated:false];
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:@true forKey:@"TG_displayedGroupTooltip_v0"];
|
|
}
|
|
|
|
- (void)tooltipTimerTick
|
|
{
|
|
[_tooltipTimer invalidate];
|
|
_tooltipTimer = nil;
|
|
|
|
[_tooltipContainerView hideMenu];
|
|
}
|
|
|
|
- (void)actionStageActionRequested:(NSString *)action options:(id)__unused options
|
|
{
|
|
if ([action isEqualToString:@"menuAction"])
|
|
{
|
|
[_tooltipTimer invalidate];
|
|
_tooltipTimer = nil;
|
|
|
|
[_tooltipContainerView hideMenu];
|
|
}
|
|
}
|
|
|
|
- (void)showGroupingTooltip:(bool)grouped duration:(NSTimeInterval)duration
|
|
{
|
|
NSString *tooltipText = TGLocalized(grouped ? @"MediaPicker.GroupDescription" : @"MediaPicker.UngroupDescription");
|
|
|
|
if (_groupingTooltipContainerView.isShowingTooltip && _groupingTooltipContainerView.tooltipView.sourceView == _toolbarView.centerButton)
|
|
{
|
|
[_groupingTooltipContainerView.tooltipView setText:tooltipText animated:true];
|
|
}
|
|
else
|
|
{
|
|
[_tooltipContainerView removeFromSuperview];
|
|
[_groupingTooltipContainerView removeFromSuperview];
|
|
|
|
_groupingTooltipContainerView = [[TGTooltipContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height)];
|
|
[self.view addSubview:_groupingTooltipContainerView];
|
|
|
|
[_groupingTooltipContainerView.tooltipView setText:tooltipText animated:false];
|
|
_groupingTooltipContainerView.tooltipView.sourceView = _toolbarView.centerButton;
|
|
|
|
CGRect recordButtonFrame = [_toolbarView convertRect:_toolbarView.centerButton.frame toView:_groupingTooltipContainerView];
|
|
recordButtonFrame.origin.y += 15.0f;
|
|
[_groupingTooltipContainerView showTooltipFromRect:recordButtonFrame animated:false];
|
|
}
|
|
|
|
if (_tooltipDismissDisposable == nil)
|
|
_tooltipDismissDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
__weak TGTooltipContainerView *weakContainerView = _groupingTooltipContainerView;
|
|
[_tooltipDismissDisposable setDisposable:[[[SSignal complete] delay:duration onQueue:[SQueue mainQueue]] startStrictWithNext:nil completed:^{
|
|
__strong TGTooltipContainerView *strongContainerView = weakContainerView;
|
|
if (strongContainerView != nil)
|
|
[strongContainerView hideTooltip];
|
|
} file:__FILE_NAME__ line:__LINE__]];
|
|
}
|
|
|
|
- (BOOL)prefersStatusBarHidden
|
|
{
|
|
return !TGIsPad() && iosMajorVersion() >= 11 && UIInterfaceOrientationIsLandscape([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]);
|
|
}
|
|
|
|
- (bool)allowGrouping
|
|
{
|
|
return _selectionContext.allowGrouping;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation TGMediaAssetsPallete
|
|
|
|
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage scheduleIconImage:(UIImage *)scheduleIconImage maybeAccentColor:(UIColor *)maybeAccentColor
|
|
{
|
|
TGMediaAssetsPallete *pallete = [[TGMediaAssetsPallete alloc] init];
|
|
pallete->_isDark = dark;
|
|
pallete->_backgroundColor = backgroundColor;
|
|
pallete->_selectionColor = selectionColor;
|
|
pallete->_separatorColor = separatorColor;
|
|
pallete->_textColor = textColor;
|
|
pallete->_secondaryTextColor = secondaryTextColor;
|
|
pallete->_accentColor = accentColor;
|
|
pallete->_destructiveColor = destructiveColor;
|
|
pallete->_barBackgroundColor = barBackgroundColor;
|
|
pallete->_barSeparatorColor = barSeparatorColor;
|
|
pallete->_navigationTitleColor = navigationTitleColor;
|
|
pallete->_badge = badge;
|
|
pallete->_badgeTextColor = badgeTextColor;
|
|
pallete->_sendIconImage = sendIconImage;
|
|
pallete->_doneIconImage = doneIconImage;
|
|
pallete->_scheduleIconImage = scheduleIconImage;
|
|
pallete->_maybeAccentColor = maybeAccentColor;
|
|
return pallete;
|
|
}
|
|
|
|
@end
|